Onion Architectureについて調べる
提唱者である、Jeffrey Palermo氏のブログを読み込んでみる
The Onion Architecture : part 1
This architecture is not appropriate for small websites. It is appropriate for long-lived business applications as well as applications with complex behavior.
オニオンアーキテクチャは小さいアプリケーションには不適であり、長くメンテされるビジネスアプリケーションや複雑なアプリケーション向け
The big drawback to this top-down layered architecture is the coupling that it creates. Each layer is coupled to the layers below it, and each layer is often coupled to various infrastructure concerns. However, without coupling, our systems wouldn’t do anything useful, but this architecture creates unnecessary coupling.
従来のトップダウン型のレイヤードアーキテクチャは、直下のレイヤーや、インフラの都合にカップリングされてしまう。動かすのに必要なカップリングというものもあるが、不必要なカップリングも生んでしまう。
The biggest offender (and most common) is the coupling of UI and business logic to data access. Yes, UI is coupled to data access with this approach. Transitive dependencies are still dependencies. The UI can’t function if business logic isn’t there. Business logic can’t function if data access isn’t there.
従来のトップダウン型のレイヤードアーキテクチャでは、レイヤーは直下のレイヤーに依存する。つまり、 連鎖的にUIがデータアクセス方法に依存してしまう。
Historically, the industry has modified data access techniques at least every three years; therefore, we can count on needing to modify data access three years from now for any healthy, long-lived systems that’s mission-critical to the business. We often don’t keep systems up-to-date because it’s impossible to do. If coupling prevents easily upgrading parts of the system, then the business has no choice but to let the system fall behind into a state of disrepair. This is how legacy systems become stale, and eventually they are rewritten.
我々の業界では、3年起きくらいに、データアクセスについてイケてる方法が現れる。長寿命かつミッションクリティカルなシステムを維持するために、3年起きくらいに追随するしかない。が、カップリングはアプデを難しくするので、アプデが結局できず、システムを修理が必要な状態のまま置き去りにする。これが原因でレガシーなシステムはリプレイスされる。
The Onion Architecture : part 1より画像引用
The main premise is that it controls coupling. The fundamental rule is that all code can depend on layers more central, but code cannot depend on layers further out from the core. In other words, all coupling is toward the center.
オニオンアーキテクチャはオニオンの中央に向かう依存は許可し、逆は許可しない。
In the very center we see the Domain Model, which represents the state and behavior combination that models truth for the organization. Around the Domain Model are other layers with more behavior. The number of layers in the application core will vary, but remember that the Domain Model is the very center, and since all coupling is toward the center, the Domain Model is only coupled to itself. The first layer around the Domain Model is typically where we would find interfaces that provide object saving and retrieving behavior, called repository interfaces. The object saving behavior is not in the application core, however, because it typically involves a database. Only the interface is in the application core. Out on the edges we see UI, Infrastructure, and Tests. The outer layer is reserved for things that change often. These things should be intentionally isolated from the application core. Out on the edge, we would find a class that implements a repository interface. This class is coupled to a particular method of data access, and that is why it resides outside the application core. This class implements the repository interface and is thereby coupled to it.
オニオンの中央にはドメインモデルが来る。ビジネスロジックや状態を表す。
ドメインモデルの周辺のレイヤーには、リポジトリ層や、サービス層が来る。
- リポジトリ層 = 永続化や取得の振る舞い(インターフェイス)
- サービス層 = ドメインモデル単体では表しきれない振る舞いを持つ
オニオンの外に向けて、変更が起きやすいものを置いていく
- UI
- データアクセスの具象(リポジトリのインターフェイスを実装)
- テスト
The Onion Architecture relies heavily on the Dependency Inversion principle. The application core needs implementation of core interfaces, and if those implementing classes reside at the edges of the application, we need some mechanism for injecting that code at runtime so the application can do something useful
オニオンアーキテクチャは依存性逆転の原則に依存しているので、アプリケーションを動かすにはどこかでDIしないといけない。
The database is not the center. It is external. Externalizing the database can be quite a change for some people used to thinking about applications as “database applications”. With Onion Architecture, there are no database applications. There are applications that might use a database as a storage service but only though some external infrastructure code that implements an interface which makes sense to the application core. Decoupling the application from the database, file system, etc, lowers the cost of maintenance for the life of the application.
オニオンアーキテクチャを使うと、「リポジトリインターフェイスを実装する手段としてDBもあるよ」くらいの存在になる。デカップリングしているので。リポジトリインターフェイスを満たせば他のストレージもOK。
The Onion Architecture : part 2
実際の事例(CodeCampServer)の紹介
MVCフレームワークであるASP.NETのアプリケーションでどうオニオンアーキテクチャを実現しているか。
public SpeakerController(IConferenceRepository conferenceRepository,
IUserSession userSession, IClock clock)
: base(userSession)
{
_conferenceRepository = conferenceRepository;
_clock = clock;
_userSession = userSession;
}
CodeCampServer uses the ASP.NET MVC Framework, so SpeakerController is part of the user interface. This controller is coupled to the ASP.NET MVC Framework, and there is no getting around that. SpeakerController depends on IConferenceRepository and IUserSession (and IClock, but we’ll omit that). The controller only depends on interfaces, which are defined in the application core. Remember that all dependencies are toward the center.
コントローラーはUIの一種なので、オニオンの一番外側。オニオンの内側(アプリケーションのコア寄り)で定義されている、インターフェイスに依存している。
- IConferenceRepositoryとIUserSession
At runtime, the IoC container will resolve the classes that implement interfaces and pass them into the SpeakerController constructor. At this point in time, the SpeakerController can do its job.
ランタイム時に、IoCコンテナがコントローラーのコンストラクタにインターフェイスを満たす具象を注入することで、コントローラーが動くようになる。
The Onion Architecture : part 3
レイヤードアーキテクチャの特徴
The Onion Architecture : part 3より画像引用
- 各層は直下の層としかやりとりしないため、一定の秩序はある。
- UI層はデータアクセス層とはやりとりしないなど
- ビジネスロジックが変更の起きやすいWeb技術やデータアクセス技術に依存してしまっている
- Web技術やデータアクセス技術は競争優位を出すことはないのに、、、
オニオンアーキテクチャ風のイラストにすると以下
- 競争優位にかかわらないが変更は多いとされる、UIやインフラが中心に存在してしまっている。ビジネスロジック含め、UIやインフラに依存するものすべてが影響を受けるため、全体として変更に弱い
The Onion Architecture : part 3より画像引用
オニオンアーキテクチャの特徴
The Onion Architecture : part 3より画像引用
- オニオンの中心にドメインモデルを置き、その周辺にビジネスロジックを置く
- 外側のレイヤーは内側のレイヤーのコードを直接呼び出せる(依存できる)
- ビジネスロジックは、データアクセスのインターフェイス(リポジトリ インターフェイス)のみを持ち、実際のデータアクセス層などはオニオンの外側に追いやられる。これにより、ビジネスロジックとデータアクセスの具体が分離される
- UI層についても同様
- テスト層についても、オニオンの外側に置くことで、テストはビジネスロジックに依存できるが、逆は無理という状態にできる
レイヤードアーキテクチャ風のイラストにすると以下。
- 上の層は下のすべての層にアクセスできる
- ビジネスロジックがインフラに依存していない
The Onion Architecture : part 3より画像引用
オニオン・アーキテクチャーの主な考え方
- アプリケーションは独立したオブジェクト・モデルを中心に構築される
- 内側のレイヤーはインターフェースを定義する。外側のレイヤーはインターフェースを実装する
- 結合方向は中心に向かっている
- アプリケーションのコアコードはすべて、インフラから分離してコンパイル・実行できる
The Onion Architecture : part 4
結局のところオニオンアーキテクチャは、変更が多いコードに依存させず、変更が低いコードに依存させるべきというただのアーキテクチャパターン。なので、単体でも使えるし、DDやCQRSなどのパターンと一緒でも使える、柔軟なアーキテクチャパターン
以下記事を読んでみる
Onion Architecture
Each layer/circle encapsulates or hides internal implementation details and exposes an interface to the outer layer
各レイヤーは自身の実装を隠し、自分よりオニオンの外にいるレイヤーが使う用のインターフェイスを公開する。
We define abstract interfaces at deeper layers and provide their concrete implementation at the outermost layer. This ensures we focus on the domain model without worrying too much about implementation details.
オニオンの内側でインターフェイスを定義し、オニオンの外側でインターフェイスを満たす具象を実装する。これによって、ビジネスロジックにて実装の詳細をきにしなくてすむ
Separation of concerns
Application is divided into layers where each layer has a set of responsibilities and addresses separate concerns. Each layer acts as modules/package/namespace within the application.
アプリケーションはレイヤーに分割され、それぞれのレイヤーは分割された責務と関心事を担う。アプリケーションだとモジュール、パッケージ、ネームスペースといった単位でレイヤーは分けられている。
Onion Architecture Layers
依存の方向 = ←
Domain Model/Entities ← Domain services ← Application services(Use cases) ← Infrastructure services
Domain Model/Entities
ドメインの登場人物、ユビキタス言語をコードにてモデル化するための手段。
Domain entities encapsulate attributes and entity behaviour.
エンティティは属性とふるまいをカプセル化する。
Onion Architectureより画像引用
Domain services
Domain services are responsible for holding domain logic and business rules. All the business logic should be implemented as a part of domain services. Domain services are orchestrated by application services to serve business use-case. They are NOT typically CRUD services and are usually standalone services. Domain services are responsible for complex business rules like computing pricing and tax information when processing order, Order repository interface for saving and updating order, Inventory Interface for updating information about items purchased, etc.
Domain serviceは、ドメインロジックやビジネスルールを保持する。全てのビジネスロジックはドメインサービスの一部として実装される。
ドメインサービスはアプリケーションサービスによって調整され、ビジネスユースケースを実現する。
単独のサービスとして動作し、通常、CRUD(作成・読み取り・更新・削除)サービスではない。
It consists of algorithms that are essential to its purpose and implement the use cases that are the heart of the application.
アプリケーションの中心となるユースケースを実現するための重要なアルゴリズムを含む。
Application services
The application services can be only invoked by Infrastructure services.
インフラ層(特にREST HandlerとかGraphQL Resolver)からのみ呼び出される。
Application services also referred to as “Use Cases”, are services responsible for just orchestrating steps for requests and should not have any business logic
Application service、別名Use caseはリクエスト処理のためのステップをオーケストレーションをする。ドメインロジックは持たない。
- domain servicesを順番に呼び出すとかして一連のリクエスト処理を実現する
Infrastructure services
Infrastructure services also referred to as Infrastructure adapters are the outermost layer in onion architecture. These services are responsible for interacting with the external world and do not solve any domain problem.
Infrastructure services、別名Infrastructure adapters。オニオンの外側に位置し、ドメインロジックは持たず、DBや外部通信などといった外部とのやりとりの具体処理を担う。
Observability services
Observability services are responsible for monitoring the application.
アプリケーションのモニタリングをする。
Testing Strategy
Different layers of onion architecture have a different set of responsibilities and accordingly, there are different testing strategies.
レイヤーはそれぞれ責務が違うので、テスト戦略も異なる。
Business rules that belong to the domain model, domain services and application services should be tested via Unit Testing.
domain model、domain service、application serviceは何かしらのビジネスルールを持つと言えるので、ユニットテストでテストする。
As we move to the outer layer, it makes more sense to have integration tests in infrastructure services
Infrastructure servicesはDBとかも関わってくるので、統合テストが良い。
For our application End to End testing and BDD are the most appropriate testing strategies.
アプリケーション全体はE2Eテストが良い。
Modularisation vs Packaging
There are two ways to organise application source code:
Either, we can have all the packages in a single module/project or
Divide the application into different modules/projects each responsible for a layer in onion architecture.
レイヤーの分け方は、1モジュールの中で複数パッケージで分ける方法と、全部モジュールごと分ける方法がある。どっちを選ぶかはプロダクトの規模とか複雑さによる。
Frameworks, Clients and Drivers
The infrastructure layer composes frameworks for web or servers, clients for databases, queues or external services. It is responsible for configuring and stitching all the external services and frameworks together. Onion architecture provides decoupling so that it becomes easier to swap technologies at any point in time.
Webフレームワークとの接続や、DBとのやりとり、外部サービスとの連携は全部 infrastructure servicesに属する。ドメインロジックに影響を与えることなく使う技術を変えられる。
Do We Need Every Layer?
It depends on the use cases and the complexity of the application. It is also possible to create more layers of abstractions depending on application needs.
ユースケースやアプリケーションの複雑さによってレイヤーを増やしても良いし、
for smaller applications that don’t have a lot of business logic, it might not make sense to have domain services
アプリケーションが小さければ、domain serviceはいらないかもしれない。
Regardless of layers, dependencies should always be from outer layers to inner layers.
レイヤーの数にかかわらず、依存の方向が守られるのが大事
Goでオニオンアーキテクチャを実際にやってみた
依存の方向を以下としている。
- Domain Model ← Repository/Domain services ← Application services(Use cases) ← Presentation/Infrastructure services
レイヤー間はすべてインターフェイス経由にしたが、大がかりであるので、やはりプロダクトの規模に応じて一定の具象への依存を許容しても良いかも。変更が少ないものに依存するという原則さえ守れて入れば。