#builderscon 2017で「複雑なJavaScriptアプリケーションに立ち向かうためのアーキテクチャ」という発表をしてベストスピーカー賞第三位を頂いてきた

先週開催されたbuildersconで表題の通りの内容をしゃべってきました。発表内容については、スライドもアップしてますが、いつもどおり実況中継シリーズを会社ブログのほうに書きます。ただ、あれ書くのすごい大変なのでもうしばらく時間かかりますすみません。書きました

自分のトークについて

弊社はまだまだメンバーが少なく、わたしもJavaScriptが専門、あるいは専任というわけではないという状況の中で、「なるべくJavaScriptに独特の概念などはプロダクトに持ち込まず、他のプラットフォームにも通用するような考え方ややり方で保守性の高いコードを書く」ということを考えて設計/実装をしています。

その試行錯誤の結果得てきた知見を対外的に発表することで、みなさんに「JavaScriptに限らず有用なトークだった」と言っていただけたのは本当に嬉しいです。

ただ、逆に考えると「JavaScript全振りマン」みたいなひとにとっては「うーんこれJavaScriptの話か?」とか「JSの話として聞くとめちゃめちゃ内容薄いっていうかJS wayじゃないよなこれ」みたいなトークになってしまったかな、ちょっとタイトル詐欺だったかな、と反省しています。

その他発表では触れられなかった話やいただいたフィードバックに対する反応は後で書く予定の実況中継シリーズのほうで触れたいと思います。

ベストスピーカー賞3位をいただいたことについて

buildersconはYAPC::Asia tokyoの流れを汲むカンファレンスですが、YAPC::Asiaの頃から何度か登壇させていただいています。で、この流れを汲むカンファレンスでベストスピーカー賞を取るというのはひとつの悲願で、一時期は「 ベストスピーカー賞狙うぞ!!」とか思ってスライドを練ったりトークを練ったりしていたこともありました。しかし、ベストスピーカー賞を狙いに行っても毎回カスリもせず……。

そんなこんなで、今年は、とにかくベストスピーカー賞のことはあまり考えず「自分が学んできたことをなるべく順を追ってわかりやすく、全体像を描きながら、設計についての情報の海に翻弄されている人の頭が整理されるようなトークをしよう」ということだけ考えてトークを準備していきました。その結果、ベストスピーカー賞をいただけたのは望外の喜びです。

そういう経緯があり、「ベストスピーカー賞は取れないしな」と思っていたので、別の用事を優先してクロージングに出席しないという蛮行に及んだ結果、せっかくの悲願であったベストスピーカー賞受賞の瞬間を逃すというまさかの事態に!!記念にそのときの一連の流れを貼っておきます。

聴いた中で一番印象に残ったトーク

pastak氏の発表が一番印象に残りました。

babelやpollyfillという武器を手に入れたわたしたちにとって、ふつうにブラウザ上で動くJSアプリケーションをクロスブラウザ対応することは一時期に比べるとだいぶ楽になりました。

一方拡張機能はどうなのか、と思っていたのですが、「やはり拡張機能を単一ソースでクロスブラウザ対応するのはすごく大変なのだな」ということ、「しかしやりようによってはクロスブラウザ対応もできるかもしれない」という希望の両方が得られる尊い発表でした。

発表後、「各ブラウザ依存のところはファイル分けてしまうみたいなアプローチも考えられると思うんですけど、そういうアプローチはどうなのでしょう?」と質問させていただきました。業務ではそのようなアプローチも取っていると聞けて、現在の状況での実践的なプラクティスと、未来への展望どちらも理解できてとても有用でした。

来年について

最近ずっと「コイツいつも設計の話ばっかりしてんな」って感じだったのですが(最近ずっとその辺を試行錯誤していたから、というのが大きいのですが)、来年は事例紹介的なトークができるといいな、と思っています。できるのか!?(事例がちゃんと成功するか?という壁と、プロポーザル出したとして採択されるのか?という壁がある)

心理的安全性を獲得しにくい個人(は|を)どうすればいいんだろう

心理的安全性ということばはだいぶ浸透してきて、とくに説明なくみんなに通じるような感じになってきているけれど、まあなかなかに難しいものだなあと思う。

組織やチームのまとめ役となる側からすると、心理的安全性が確保できるチームをどのように作っていくか、という点に注目があつまりがちなんだけど、個々人が心理的安全性を獲得するときにも、それぞれの性格によって難易度に差があるよね、ということについて書きたい。言葉を変えると、多くのひとが問題なく心理的安全性を獲得できるような状況やチームであっても、自己肯定感を適切に持つことが難しいようなタイプのひとは、なかなか心理的安全性を獲得できないという問題(問題、というと語弊があるかもしれないけど、problemというよりもissueという意味での問題)があるんじゃないか、というような話だ。

自分にそういうところがあるからこの話をするのだけれど、わたしは「他人が自分をどう評価するのか」「他人が嫌な気持ちになっていないか」ということを過剰に気にする傾向がある。たちがわるいのが、あくまでそれは自己完結した自分の感情であり、この気持が外に向かってコミュニケーションを改善する方向に向かず、自分の中でぐるぐる回って自家中毒を起こすような方向に向かってしまうところだ。

他人は他人であり、それを理解することはそもそもできない。できないからこそ、わたしたちは言葉その他様々な方法で相手にメッセージを送り、それを受け取り、その繰り返しで信頼関係を築いていくわけだ。けど、「他人が自分をどう評価するのか」「他人が嫌な気持ちになっていないか」を過剰に気にしてしまい、しかもその気持ちを適切に外に向けることができないと、「明らかにこれは無限に友好的なメッセージである」と判断できないメッセージを受け取ったとき(つまり大抵の場合)に、「なにかわたしが相手の機嫌を損ねるようなことをしてしまったのだろうか」と不安になり、萎縮しはじめる。結果として、こちらからきちんとメッセージを発信することができなくなり、コミュニケーション不全に陥る。

さらに悪いことに、普段そうやってビクビクしているからこそ、逆に「このひとはわたしにとって安全だ」となってしまうと、そこに甘えて雑なコミュニケーションを行ったり「言わなくてもわかってもらえるよね」みたいな感じになってしまう(そうして、結果としてせっかく良い関係を築けていたところを自分からぶちこわしてしまう)。こっちの問題については最近思うところがあって、ちゃんと努力して実際改善しつつあるとは思っている(まだがんばっている途中)んだけど。

で、本題に戻ると、こういうタイプのひとは、上述のような流れで、自分にとって「判断不能」なメッセージをひとつ受け取ると、その解釈不能性が自家中毒を起こしはじめる。そして、たったひとつの、ほかの人からしてみたらなんてことないメッセージだけで、おおいに心理的安全性が脅かされるということになりがちだと思う。自分がそういうタイプであるとき、あるいはメンバーがそういう問題を抱えているときに、どうやったらこれを改善あるいはサポートし、健全なコミュニケーションを取り戻すことができるのか、ということを最近よく考える。

とはいえ、まず自分の問題を解決しないことには他人の問題を解決することはできないわけで、まずは自分のこの問題とどのように付き合っていくのかについて、なんとかしないといけないよなあと思っているのであった。自分の問題を確認するために言語化したかっただけで、結論はまだない。

第52回NDS(長岡開発者勉強会)で「怖くないし役に立つ設計原則の話」を発表してきました

新潟県長岡市で開催された、NDSの第52回で、「怖くないし役に立つ設計原則の話」というタイトルで発表してきました。

内容としては、

  • 設計原則は「馬鹿の一つ覚え」でやっていくとむしろ保守性を下げてしまうことを確認する
  • 様々な設計原則について例を出しながら、別の設計原則との有機的なつながりを考る
  • 有機的なつながりを意識しながら設計原則を利用することで、保守性の高い設計を導く

というような内容でした。少し誤解を生む表現などが入っているため資料は公開しませんが、機会があればどこかで再演したいなと思っています。お誘いください。

聞いた発表では、 @yuw27b さんの「今日から使えるCSSパターン」が最も興味深かったです。

保守性の高いCSSを書くのがものすごく難しいなか、どうやってCSSの難しさに立ち向かっていくかという話でしたが、BEMに触れつつ「DRYさを諦めてでも結合度を下げたほうがよい」という話題になったのは「なるほどな〜」という感じがしました。

わたしがCSSに触れて「怖いな」と思う部分は、「全部グローバル変数みたいなもん」というところです。そこにBEMを導入することで、擬似的にスコープを切れるようになるので、BEMはかなり筋のよい手法だと感じています。でも、それを考えるともしかしてつまり本当に求められているのはscoped cssでは?という感じがするし、最近のwebフロントエンドのコンポーネント指向も、scopedなCSSと同様の方向を向いてきているように感じています。そこで「Web Componentsはよ!」という気持ちになってきますが、なんというか、現実はなかなか厳しいですね……。大変であります。

NDSプログラミング言語や分野などが制限されていないため、毎回結構「異種格闘技戦」な様相を呈しており、自分の全然知らないことに出会えたり、学びがあってとても良いですね。またぜひお邪魔させていただきたいと思います。

#y8spring でフロントエンド開発の話をしてきました

先日行われた #y8spring で、@ushiboyさんとともにフロントエンド開発についてのトークをしてきました。

懇親会会場でお酒を飲みながらトークするという、いわゆる「はちぴースタイル」での発表だったため、かなり会場が温まっていてありがたかったですが、わたしにも酒が入っていてグダグダになってしまい、アルコールがダメのためシラフであった相方の@ushiboyさんには申し訳ないことをしたな!?と思っております。ご感想お待ちしております。

以下は聴衆としての感想ですが、ジョージさんによるトークがとてもよかったです。

hyperapp – 1kbのビューライブラリ · Issue #11 · uzulla/y8-2017-spring-talks · GitHub

reduxとかでいうところのreducerに当たる、State => Stateな関数をactionが持てると聴いたときにわたしがまず思ったのは「となるとreduxとおなじく非同期に対する問題が立ち上がってくるよなあ」ということでした、トークの中ではその話に触れられていなかったので、「非同期周りってどうしてるんですか」という質問をしたところ、hyperappはState => Promise[State]な関数をactionとして持てたり、actionの中で別のactionを発火することもできるとのことで、「うーん筋の良い感じのスタイルに思える」と感じました。

ところで、ヤパチーは今回は1トラックでの開催でしたが、1トラック結構いいですね!強制的に(?)様々なトークが聞けるし、「あれの裏番組がそれで、どっちも見たいのに!」とか考えなくていいし、懇親会でも多くのひとが同じコンテキストを共有できるし、巨大カンファレンスではない良さみたいなものがあってよかったですね。

こちらからは以上です。

オフラインファースト的なGUIアプリケーションをScala.jsで書く話 / write stack - usecase

前回の記事では、Scala.jsで書かれたモデル層がどのようなクラス/オブジェクトをJSで書かれたUI層に公開し、UI層はそれらをどのようにして扱うのかというのを見てきました。今回からはScala.jsで書かれたモデル層のうち、コマンドから始まる一連の状態更新系(write stackと呼んでいます)について見ていきたいと思います。

write stackを構成するpackageとクラスたち

write stackを構成するクラスは以下の通りです

  • usecase パッケージ
    • Commandクラス
    • Serviceクラス
  • domain パッケージ
    • Domain Modelを表すクラスたち
  • infrastructure パッケージ
    • Repositoryクラス
    • Synchronizerクラス

このうち、今回はusecaseパッケージに注目してみましょう。

usecaseパッケージは、いわゆる「アプリケーション層」に相当するパッケージです。アプリケーション層の責務は、アプリケーションへの入力を受け取り、domaininfrastructureにメッセージを送り、ユースケースを実現する責務です。

逆に言うと、この層に「ユースケースを実現する」以外の関心が書かれてしまうと「ドメインモデル貧血症」を起こすことになりますので、この層は薄く実装されるべきです。

write stackでは、CommandクラスとServiceクラスがこのusecaseパッケージを構成しています。

Commandクラスの責務

Commandクラスの責務は、以下のふたつの責務を持っています。

  • ユーザー入力のvalidation
  • ユーザー入力をアプリケーション上意味の意味のある値に変換
  • Serviceのdispatch

わかりやすい例はAddTodoCommandでしょう。

trait Command {
  protected val addTodoService: Service

  private var _todoInput = ""
  private var _dueDateInput = ""

  def todoInput = _todoInput
  def todoInput_=(v: String) {
    _todoInput = v
  }

  def dueDateInput = _dueDateInput
  def dueDateInput_=(v: String) {
    _dueDateInput = v
  }

  def isTodoInputValid = _todoInput != ""
  def isDueDateInputValid = {
    val ret:Boolean = try {
      dueDate
      true
    } catch {
      case _:DateTimeParseException => false
    }
    ret
  }

  def isExecutable = isTodoInputValid && isDueDateInputValid

  def dueDate = {
    LocalDate.parse(dueDateInput)
  }

  def execute() = addTodoService.execute(this)
}

todoInputdueDateInputは、フォームの値が変更されたらUI側からここに値がぶっこまれてきます(前回見たとおりですね)。

is*Valid系のメソッドがユーザの入力値の妥当性を検証しています。この値をUI側から読み出すことで、エラーメッセージの表示などができるわけですね。

また、dueDateInputString型ですが、アプリケーション内ではStringではなくてLocalDate型で扱いたいですよね。その変換もこのクラスが行っています。

また、executeメソッドでは自身を引数にServiceをdispatchしています。

このaddTodoServiceが抽象メンバー(って正しい言葉なのかな、わかんない)であるところがちょっとしたポイントですね。Commandクラスが単体テストしやすいように他のクラスへの依存は抽象メンバーとして定義しておいてDIするわけです。DIについてよくわからない、という向きは手前味噌ですが要するに DI って何なのという話がわかりやすいと評判なのでおすすめです。

ちなみに、Scala.jsで定義されたクラスなどをJS側から触るためには、@JSExportの仲間たちのアノテーションを付けてやる必要がありますが、今回はjs_bridgeというパッケージに「JS側にexportするクラス」を置いています。見てみましょう

@JSExportTopLevel("AddTodoCommand")
class AddTodoCommand extends Command {
  protected val addTodoService = new ServiceImpl

  @JSExport
  override def todoInput = super.todoInput
  @JSExport
  override def todoInput_=(v: String) = super.todoInput_=(v)


  @JSExport
  override def dueDateInput = super.dueDateInput
  @JSExport
  override def dueDateInput_=(v: String) = super.dueDateInput_=(v)

  @JSExport
  override def isTodoInputValid = super.isTodoInputValid
  @JSExport
  override def isDueDateInputValid = super.isDueDateInputValid

  @JSExport
  override def execute() = super.execute()
}

さきほどはCommandをtrait(多重に継承できる抽象クラスみたいなもんです)として定義して、addTodoServiceは抽象メンバーでしたね。それを継承してJS側にExportするタイミングで、実際のServiceの実装を配線してDIしているのが見て取れるでしょう。

また、publicなメソッドであっても明示的に@JSExportしないとJS側からはそのメソッドをさわれないため、exportするメソッドは明示的にoverrideしています。しかしここには「実装」は一切書かれておらず、ロジックはすべてピュアScala.js(って言い方で通じるかな)であるtrait側に書かれていることに注意してください。

Serviceクラスの責務

では、CommandクラスによってdispatchされるServiceクラスは具体的にどのような仕事をしているでしょうか。「todoを追加する」というユースケースを実現するusecase.add_todoパッケージのServiceを見てみましょう。

trait Service {
  protected val repository: TodoRepository
  protected val synchronizer: TodoSynchronizer

  def execute(command: Command): Unit = {
    if ( ! command.isExecutable ) {
      return
    }

    // ...(1)
    val todo = Todo.open(
      id = repository.nextId(),
      body = command.todoInput,
      dueDate = command.dueDate
    )

    repository.store(todo) // ...(2)
    synchronizer.sync(todo) // ...(3) 
  }
}

domain層で定義されているTodoというドメインモデルを利用して、Todoを新しく作っています(1)。新しく作られたTodoを、アプリケーションの状態を保持する役目を持つrepositoryに保存しています(2)。参照系を構成するread stackの説明をするときに詳述しますが、read stackでこのrepositoryに保存されている状態を読み取ることで、UI側はアプリケーションの論理的な状態をUIとして描画する形です。

また、今回のアプリケーションでは「新しいTodoを作ったらなるべくすぐにサーバーに同期する」という仕様にしてあるので、「同期してくれる君」であるsynchronizerにtodoの同期を頼んで(3)、このクラスの責務はおしまいです。

このとき、サービスは「新しく作られたTodoがどのような状態やプロパティを持っているのか」や「repositoryはTodoをどこに保存しているのか」や「synchronizerはどうやってtodoをサーバーと同期しているのか」の詳細については一切意識していないことに気をつけてください。

次回詳述しますが、「新しくTodoが作られたときにそのTodoはどのような状態なのか」とか、そういうのはdomainパッケージに記述していきます。

このあたりの話の具体例をもう一つ出しましょう。「todoをdone状態にする」というユースケースを実現するusecase.make_todo_done.Service

trait Service {
  protected val repository: TodoRepository
  protected val synchronizer: TodoSynchronizer

  def execute(id: Int) = {
    repository.find(id).foreach {oldTodo =>
      val doneTodo = oldTodo.makeDone
      repository.store(doneTodo)
      synchronizer.sync(doneTodo)
    }
  }
}

repositoryから該当のtodoを引っ張ってきて、そのtodoに対してmakeDoneというメソッドを呼び出して「doneになったTodo」をrepositoryに保存して、同期してるだけです。「todoにどういうプロパティがあるのか」とか一切意識していません。

次回予告

次回は、usecaseパッケージが利用しているdomainパッケージとinfrastructureパッケージののうち、domainパッケージが持つ責務を具体的に見ていく予定です。

NDS(長岡開発者勉強会)の52回で「怖くないし役に立つ設計原則の話」というタイトルで喋ります

新潟県長岡市では定期的に「NDS」という勉強会が開催されています。わたしも新潟に住んでいるときには非常にお世話になった勉強会で、とても良い勉強会です。

その第52回(52回ですよ!?すごくないですか!?)が6/17に開催されるとのことなので、「怖くないし役に立つ設計原則の話」というタイトルで発表させていただくことにしました。

nagaoka.techtalk.jp

まだ内容固まっていないのですが、このトークでは、

  • デザインパターンとか知識としては知ってるんだけどなぜそれが有効なのかよくわからん
  • 「結局設計パターンとかって役に立たないし勘でやるものでしょ」
  • MVCとかMVVMとかMVPとか「わざわざ難しくしてる」としか思えない!!!
  • DRY原則なら知ってる

といった感じのひと(2010年くらいの俺だ……)を想定して、

  • われわれは(主語が大きい)なぜ設計パターンを役立たせることができないのか
  • 設計パターンには抽象度の高いものから低いものまで様々なものがある
  • SOLID原則を例にとって「役に立たせ方」を実践する

といった内容を発表したいと思っています。チャレンジング!どこまで成功するだろうか。

長岡にはおいしいお酒もおいしい料理もあるので、みなさんもぜひこの機会にNDSに参加してみてはいかがでしょうか。お待ちしております。

オフラインファースト的なGUIアプリケーションをScala.jsで書く話 / Vue.jsによるUI層とScala.jsによるモデル層のコミュニケーション

前回の記事では、Scala.jsをどのように利用したかについて概観を見てきました。

前回、UI層は素直にVue.jsの単一ファイルコンポーネントで書いて、モデル層はScala.jsで書く、というスタイルを取る、と述べましたが、今回はモデル層がどのようなインターフェイスをUI層に公開して、UI層がどのようにそれらを利用する設計としたのかについて見ていきます。

モデル層の公開するみっつの種類のクラス/オブジェクト

モデル層は、UI層に対して、「コマンド」「クエリ」「イベント」のみっつの種類のクラス/オブジェクトを公開します。まずは「コマンド」から見ていきましょう。

コマンドの責務

コマンドは、アプリケーションの状態を更新するような操作(たとえばTodoを追加する、Todoをdone状態にする、など)の窓口を提供します。

なにかを更新するような操作を行う場合、UI層は、

  • コマンドをインスタンス化し
  • コマンドを組み立てて
  • コマンドを実行

します。また、コマンドクラスは「このコマンドはvalidか?」を判別する責務も持ちます。これがUI側からどのように見えるのかがよくわかるのがTodoInput.vueです。JS部分を引用してみます。

    import {AddTodoCommand} from '../../../target/scala-2.12/scalajstodo-fastopt' //...(1)
    export default {
        beforeCreate(){
            this.addTodoCommand = new AddTodoCommand; //...(2)
        },
        data(){
            return {
                todoInput: "",
                dueDateInput: "",
                isTodoInputValid: true,
                isDueDateInputValid: true
            };
        },
        watch: {
            todoInput(v){ // ...(3)
                this.addTodoCommand.todoInput = v;  //...(4)
                this.isTodoInputValid = this.addTodoCommand.isTodoInputValid; //...(5)
            },
            dueDateInput(v){
                this.addTodoCommand.dueDateInput = v;
                this.isDueDateInputValid  = this.addTodoCommand.isDueDateInputValid;
            }
        },
        methods:{
            addTodo(){ //...(6)
                this.addTodoCommand.execute(); 
            }
        }
    }

まずは(1)の部分で、Scala.jsから公開されているAddTodoCommandというクラスをimportして、(2)でコマンドをインスタンス化しています。

フォームのinputに変化が起こると、データバインドされたtodoInputに変化が起こり、それをwatchしている関数(3)が呼び出されるので、その中でコマンドの値をセットています(4)。さらに、コマンドクラスは「この値がvalidな値かどうか」を知っているので、「この値はvalidですか?」というのをコマンドクラスにたずねて、その結果をUI側に書き戻しています(5)

フォームがsubmitされると、addTodoというメソッドが呼び出されます(6)。この中では、組み立て終わったコマンドを実際にexecuteしているだけです。この際、コマンドは投げっぱなしになっていることに注意してください。

コマンドクラスは「アプリケーションの状態を更新する」ための窓口であり、「更新されたあとの状態を取得する」ための窓口ではありません。「更新結果の取得」にはQueryを利用します。

クエリの責務

UI上にアプリケーションの現在の状態を描画するためには、現在の状態をなんとかして取得しなければなりません。その際に利用するのが、Queryです。これもUI側からの利用例を見ましょう。TodoList.vueです

    import {
        TodoQuery,
        /* 中略 */
    } from '../../../target/scala-2.12/scalajstodo-fastopt'
    
    export default {
        beforeCreate(){
            this.todoQuery= new TodoQuery; //...(1)
        },

        /* 中略 */

        data(){
            return {
                todos: this.todoQuery.all() //...(2)
            };
        }
    }

(1)インスタンス化したqueryを利用して、(2)でアプリケーションの状態(今回ならば「現在のtodo一覧」)を取得して、databind用の変数に書き戻しています。

ところで、このqueryは「いつ」実行すべきでしょうか。(2)の部分では、まず、UIの構築時に「初期データ」としてアプリケーションの状態を読み出しています。

しかし、よく考えるとそれだけではなくて、「アプリケーションの状態に変化があったとき(つまり今回ならtodoのリストに変化があったとき」にも、状態を読み出してUIを更新しなければなりません。そこで必要になるのが「イベント」です。

イベントの責務

イベントは、モデル側でなにか状態に変更が起こったのをUI側に伝えるためのオブジェクトです。これも例を見るのが一番でしょう。クエリと同じく、TodoList.vueです。

    import {
        /* 中略 */
        TodoRepositoryChanged //...(1)
    } from '../../../target/scala-2.12/scalajstodo-fastopt'
    export default {
        /* 中略 */
        created(){
            // (2)
            this.subscription = TodoRepositoryChanged.subscribe(() => {
                this.todos = this.todoQuery.all();
                console.log(this.todos);
            });
        },
        beforeDestroy(){
            this.subscription.unsubscribe();
        },
        /* 中略 */
    }

TodoRepositoryChangedというイベントをScala.js側からimportしてきています(1)。さらに、(2)でそのイベントが発火したらtodoを読み込み直すようにしています。UIがdestroyされたときにきちんとunsbscribeするのも忘れないようにしましょう(3)

UI層とモデル層のコミュニケーションまとめ

以上をまとめると、以下のようなフローでUI層とモデル層はコミュニケーションすることになります。

まず、初期データをQueryを通じて読み込みし、UI側でレンダリングします。

f:id:nkgt_chkonk:20170522150803p:plain

状態の更新を伴うような操作は、Commandを組み立てて実行します。

f:id:nkgt_chkonk:20170522150924p:plain

Commandの結果としてアプリケーションの状態が変化し、Eventが通知されます

f:id:nkgt_chkonk:20170522151017p:plain

Eventに応じて、最新の状態を読み出し、表示します。

f:id:nkgt_chkonk:20170522151058p:plain

このように、モデル層では「更新の窓口」と「読み出しの窓口」と「状態通知の窓口」のみっつを設けて、UI層ではそれらを適切にimportし利用することで、「UIの定義」と「アプリケーションのロジック」を分離することができます。いわゆる「プレゼンテーションとドメインの分離」ってやつですね。

次回予告

次回は、オフラインファーストなアプリケーションにおいて、モデル層をどのように設計していったかについて書きます。