導入
アーキテクチャに関しての勉強をする傍ら、アウトプットの一環として学んだことを記事にまとめようと思っていたのですが、
アーキテクチャを上手くまとめてくれている記事はごまんとあるので、この記事では、自分がアーキテクチャを勉強する中で感じた疑問に自分で答えていくことで、みなさんも同じく持っているかもしれない疑問に答えることができれば幸いです。
また、こちらの記事には新たに自分が見つけた疑問も追記していこうと思っていますので、
時折覗きに来ていただけると嬉しいです(笑)
アーキテクチャ全般
アーキテクチャはそもそもなぜ必要なのか?
サービスの保守性と拡張性の確保のため
プロジェクトの中でどの箇所のコードがどの処理を請け負うかが明らかになり、開発に規則を生むことがつながり、保守性と拡張性を高めることが出来る。
適切なアーキテクチャを選定しないで開発を行うと、プログラマーごとに好きなようにコードを書いてしまうため、受け渡すデータの加工がどこで行われているか等、保守・改修にかかるコストが大幅に増える可能性が高くなる。
また、レイヤーごとに処理をまとめることで、そのまとまりごとにテストを行うことが可能となるが、
こちらもアーキテクチャをしっかり組んでいない場合には、適切な粒度でテストコードを書くことすら難しくなってしまうため、品質の担保ができなくなってしまう。
レイヤードアーキテクチャ
複雑だし処理に時間かからないのか?
特別な構成の下作られていないモノリス(後述)と比べたらパフォーマンスは低くなりやすい。
文字通りレイヤーに分かれており、モノリスよりも1つの操作に対して経由する箇所が多いため当然のごとくパフォーマンスは比較的低くなりやすい。
ただしプロダクトの保守性は高まりやすいため、プロダクト自体の非機能要件の優先度をどこに置くかでアーキテクチャの選択を行うべき。
アプリケーション層とドメイン層の違いは何?
アプリケーション層
アプリケーション層ではUI層で指定された操作に必要なドメイン層のメソッドを呼び出して実行してもらったり、ドメインから返ってきた値をUI層に返して描画を行ってもらう
ドメイン層
ドメイン層では、必要なエンティティが定義されている
※djangoで言うところのモデルが定義されていて、モデルごとに必要なメソッド(CRUD的なもの)も定義している。
ただし、実際にDBへのアクセスを行うのはインフラ層の責任範囲のため、ドメイン層ではメソッドとして何を用意するかを書くに留まる。
何が優れているのか?
層に分かれているから、開発する時に担当するべき責任の範囲が決まっているために、保守性が高くなる
また、層ごとに期待されるインプットとアウトプットが明らかになっているため、テストを行うのも容易になる
DDDと何が違うのか?
依存関係の終端にいるものが違う
インフラに依存しているのがレイヤード
ドメインに依存しているのがDDD
従来のレイヤードだと、依存関係の終端にいるのがインフラ層のため、DBがPostgresからMySQL変わったときなど、インフラに変更があるとCRUDの処理がインフラに依存しているために、インフラ層だけに変更が留まらない。
DDDであればドメインが依存関係の終端にいるため、インフラが変わっても、コードの変更はインフラに確実に留まる。
DDDの場合とレイヤードの場合とで出る差については、今すぐ「レイヤードアーキテクチャ+DDD」を理解しよう。(golang)の記事でよく解説されています。
ぜひ参考にしてみて下さい。
DDDで「ビジネス(ルール)」という言葉を目にするがどういった意味で使われているのか?
筆者はアーキテクチャの勉強の中で「ビジネス」という言葉が出てくるたびに、自分の知っている「ビジネス」という単語と同じ意味で使われているのか疑問に思っていました。
結論から言えば「同じ意味ではない」と個人的には思っています。
DDDで使われるビジネスとは、あるエンティティがどういう動作をするのか、その動作に起因して他に何が連動するのか といった情報や規則性を指していると考えます。
例えば、ECショップにおいてユーザーが商品の購入を完了したタイミングで、商品の在庫数を減らしたり、ユーザーの購入履歴のデータを更新するといった付随する処理が必要になります。
このように、あるアクションがそもそもどのように実行されるのか、それに付随してどんな処理が行われるか、などの情報のことをビジネス(ルール・ロジック)などと呼んでいると認識しています。
依存性の注入とは?
レイヤードアーキテクチャの勉強をしていると何かと出てくる言葉
コチラ(DI:依存性の注入とは何か?)の記事の言葉を借りれば、それぞれのレイヤーが成立するために必要な情報を依存先のレイヤーから"注入"してもらう ことを指しています。
例えば、ユースケース層がレイヤーとして成り立つ(UI層に使ってもらうことが出来る状態になる)には、依存先であるドメイン層から必要なエンティティ(Djangoでいうモデルとそれに付随するメソッド)の情報を渡してもらう必要があります。
実際の例としてはコチラ(DDDを意識しながらレイヤードアーキテクチャとGoでAPIサーバーを構築する)の記事でも軽く触れられているように、それぞれのレイヤーのインターフェースが必要とされているレイヤーに引数として渡されるような形となっています。
メリットは、テストの容易性や、必要なインプットとアウトプットが明確になることだと考えています。
参考:DI(依存性の注入)とは依存性を注入するということである、、?
聞きなれていないとイメージをつかむのが難しい言葉ですが、個人的な感覚として、ハードコーディングを避けるべき理由が理解できていれば、依存性の注入の概念もある程度理解しやすいのではないかと思います。
プレゼンテーション層とバックエンドは同じ言語でもいいか?
もちろんOK、筆者は「Javascriptのライブラリとかで書かなければいけない」と勝手に思っていたが、JSのライブラリを使おうが使うまいが、どちらでも良い。
大事なのは、レイヤーに分かれているかどうか。
そのため、Djangoでバックエンドを書いていて、フロントもDjangoだったとしてもレイヤードの要件は十分満たせる。
UIとバックエンドは同じサーバーにするべき?
任意
ここでも同じようにレイヤードの定義から言えば、ポイントは レイヤーに分かれているかどうか のため、サーバーが分かれていても同じでも構わない。
つまり、Djangoでアプリケーションを作り始めて、Reactをライブラリとして使ってもいいし、Next.jsで別アプリケーションとして立てても良い。
マイクロサービス
いくつにもサービスが分かれているが認証はどうやっている?
例えば、こちらのSock Shop(マイクロサービスのサンプルプロジェクト)では、UIサービス(Javascript)がバックエンドの認証サービス(Golang)とやりとりしながら、ブラウザのCookieにユーザーの認証情報をつけることで、それ以外のサービスを使う時の認証を行っている。
このSock Shopの場合、商品をカートに入れるなど、画面上の全ての操作がUIサービスに設置されたエンドポイントをから行われるため、UIサービスでログインしていないユーザーをブロックすればOKとなっている(仕組みの名前的にはAPIゲートウェイと呼ばれるものです)。
ただし、実際のケースではSock Shop以外で行われている方法以外での認証も可能で、その場合はマイクロサービスごとに認証を行うなど、いくつかの方法で個々のサービスの利用時に認証を行っている。
(執筆時点で理解が浅い部分なので後ほど記載変更の可能性あるかもです...)
参考:Microservices における認証と認可の設計パターン
サービスの粒度はどう決めるべきか?
ケースバイケースではあるが、
DBで言うテーブル単位でサービスを持っている場合が多い印象。
本当に細かいとPDFの出力だけ、みたいに局所的にサービスを作っている場合もある。
なお、下記の参考記事では、
「分割することで開発効率は向上するか」
「各サービスの独立性は高いか(あるサービスの変更影響が他のサービスに波及しないか)」
「サービス分割により技術的な選択肢が広がるか」
といった点を考慮すべきと書かれている。
個人的には、マイクロサービスの狙いの一つである、「密結合を避ける」というのを前提とした上で、一旦可能な限りサービスを分解して、その中から密結合を許容するべき組み合わせや、上記の通り、開発効率の向上につながる組み合わせのみを同じサービスに入れるという考え方がいいように感じた。
参考:マイクロサービスアーキテクチャの特徴を学ぼう! メリット・デメリットや設計の指針とは
レイヤードとの併用はありえるのか?
ありえる
と言っても、マイクロサービスの各サービスの中でレイヤードを構築するのではなく、レイヤードの各層をマイクロサービスで構築する、といったものになることが多い様子。
言い換えれば、マイクロサービスとレイヤードアーキテクチャは、「どちらか一方を選択しなければいけない」というものではない。
参考:マイクロサービスにクリーンアーキテクチャを採用する上で考えたこと
マイクロサービスで開発を行うメリットは?
大人数での開発時でメリットが発揮される
見方を変えればデメリットでもあるが、マイクロサービスはサービス単位でチームメンバーを組めるため、プロダクト全体の数でみれば、大規模なエンタープライズレベルのチーム開発に適したアーキテクチャであると言える。
(つまり、頭数が少ないとそもそも開発効率が遅くなるだけで恩恵を受けにくい)
そして、このマイクロサービスアーキテクチャであれば、
サービスごとに独立しているために、それぞれのサービスで違う言語で開発を進めたり、
コンフリクトを気にせずにコードを書けたりする。
障害時の影響が限定的になる
デプロイのパイプラインがエラーになったときなど、万が一どこかのサービスで障害が起きてもプロダクト全体ではなく局所的なものにとどめることができる。
デメリットはあるのか?
インフラの管理に工数が割かれる
マイクロサービスは、文字通りいくつものサービスの集合体となっているため、それぞれのサービスのインフラの管理も必要になってくるために、そこで工数がかさむ。
くわえて、サービスの間ではAPI通信を行うために、エラーポイントが多くなる可能性も増える。
サービス間でのDBアクセスの難化
マイクロサービスでは、あるサービスが別のサービスで使われているDBにアクセスする場面がありえる。
この時、そのDBに対してアクセスするサービスが複数となってしまい、データの整合性が保たれない可能性がある。(いわゆるACIDにのっとって処理が行われない状態のこと)
マイクロサービスではこの状況への対処も考慮が必要となる。
参考:マイクロサービスに移行した際の分散トランザクションの危険性
モノリス
レイヤードやマイクロサービスにないメリットは?
コード量の少なさ
他アーキテクチャに比べると、コードの量が少なくて済ため、小規模のアプリケーションの開発であれば恩恵は受けやすい。
処理の速さ
レイヤードやマイクロサービスは一つの処理が終了するまでにいくつものレイヤーやサービスにアクセスする必要がある。
一方でモノリスはより単純なため、処理速度は速くなりやすい。
参考:[翻訳] Shopifyにおけるモジュラモノリスへの移行
MVC
MVCはアーキテクチャの一種なのか?
一種ではあるが着目点が違う
MVCもアーキテクチャの一つではあるが、レイヤードアーキテクチャで言うところのプレゼンテーション層内の構成にフォーカスが当たっている。
MVCの「M」はレイヤードアーキテクチャにおけるアプリケーション層~インフラ層にあたり、「VC」はプレゼンテーション層を担っていると考えることができる。
参考:MVC、3 層アーキテクチャから設計を学び始めるための基礎知識
他にも競合するものとしてはMVVMやMVPなどがあるが、いずれにしてもポイントはプレゼンテーション層内の構成をどうするか、という話になるのでレイヤードやマイクロサービスとは着目点が変わってくる。