概要
クリーンアーキテクチャは以下の五つを実現して、関心の分離ができることが大きな利点です。
これらを実現するために必要な実装を考えていきます。
この記事はWebフレームワークの使用を前提としており、例ではGo言語のGinを使います。
- フレームワーク独立。アーキテクチャは、機能満載のソフトウェアのライブラリが手に入ることには依存しない。これは、そういったフレームワークを道具として使うことを可能にし、システムをフレームワークの限定された制約に押し込めなければならないようなことにはさせない。
- テスト可能。ビジネスルールは、UI、データベース、ウェブサーバー、その他外部の要素なしにテストできる。
- UI独立。UIは、容易に変更できる。システムの残りの部分を変更する必要はない。たとえば、ウェブUIは、ビジネスルールの変更なしに、コンソールUIと置き換えられる。
- データベース独立。OracleあるいはSQL Serverを、Mongo, BigTable, CoucheDBあるいは他のものと交換することができる。ビジネスルールは、データベースに拘束されない。
- 外部機能独立。実際のところ、ビジネスルールは、単に外側についてなにも知らない。
参考: https://blog.tai2.net/the_clean_architecture.html
それぞれを実現するために
1. フレームワーク独立
フレームワーク独立にはクラス図のController, InputData, UseCaseInteractorが関わっています。
フレームワーク独自の情報をビジネスルール(UseCaseInteractor)が扱わないようにすることで、ビジネスルールがフレームワークに依存しなくなり、フレームワーク独立を満たすことができます。
そのためにControllerがフレームワークから受け取った情報をビジネスルールが必要とする情報(InputData)に変換してからビジネスルールに渡すようにします。
実装
ヒジネスルールが必要とする情報の構造体を定義します
type CreateUserInputData struct {
name string
email string
}
先ほど定義した構造体を引数にしたメソッドを持つ、ビジネスルールのインターフェースを定義
type CreateUserInputBoundary struct {
Exec(CreateUserInputData)
}
ビジネスルールのインターフェースを満たすビジネスルールを定義
type CreateUserUseCaseInteractor struct {
// 省略
}
func (u *CreateUserUseCaseInteractor) Exec(input *CreateUserInputData) {
// 省略
}
ビジネスルールのインターフェースを持つControllerを定義
Controllerにはフレームワークからの情報を引数にした、UseCaseInteractorを呼び出すメソッドを定義する
type UserController struct {
createUser CreateUserInputBoundary
}
// Userを作成するコントローラーのメソッド
// ctx: フレームワークから受け取る情報
func (u *UserController) Post(ctx *gin.Context) {
// ビジネスルールが必要とする形に変換
inputData := NewCreateUserInputData(ctx.PostForm("name"), ctx.PostForm("email"))
// ビジネスルールに情報を渡す
u.createUser.Exec(inputData)
}
Input Dataの構造体を用意しないパターン
UseCaseInteractorの引数がフレームワーク独自の情報でなければ、InputDataという構造体を用意しなくてもフレームワーク独立を満たすことができます。
この場合、クラス図はこのようになります
2. テスト可能
DataAccessとPresenterをMockやStubに置き換えられるようにすることで、外部要素なしでUseCaseInteractorのテストができるようになり、テスト可能を満たすことができます。
DataAccessのMock化にはUseCaseInteractor, DataAccessInterface, DataAccessが関わってきます。
DataAccessはビジネスルール(UseCaseInteractor)が外部要素にアクセスする機能を切り出したもので、UseCaseInteractorが外部アクセスしたいときはDataAccessを呼び出します。
実装する際はUseCaseInteractorにDataAccessInterfaceをDIして、DataAccessInterfaceから機能を呼び出します。
DataAccessではなく、抽象化したDataAccessInterfaceをDIすることで、呼び出す先をDataAccessのMockに置き換えられるようにします。
Presenterも同様で、UseCaseInteractorにPresenterを抽象化したもの(OutputBoundary)をDIすることでMockに置き換えられるようにします。
実装例
実際にUseCaseInteractorがDataAccessのMockを呼び出すようにしてみましょう。
DataAccessの定義
// 外部要素にアクセスする機能(DataAccess)
type UserDataAccess struct {
// 省略
}
// 実際に外部にアクセスしてUserを作成する
func (*UserDataAccess) Create(user *entity.User) error {
// 省略
}
// 外部要素にアクセスする機能のMock
type UserDataAccessMock struct {
// 省略
}
// 外部にアクセスせず、成功したことだけ返す
func (*UserDataAccessMock) Create(_ *entity.User) error {
return nil
}
UseCaseInteractorとDataAccessInterfaceの定義
// 外部要素にアクセスする機能を抽象化したもの(DataAccessInterface)
type UserDataAccessInterface interface {
Create(user *entity.User) error
}
// ビジネスルール(UseCaseInteractor)
type UserUsecaseInteractor struct {
// 外部要素にアクセスする機能を抽象したもの
userDataAccess UserDataAccessInterface
}
// Userを作成するビジネスルール
func (u *UserUsecaseInteractor) CreateUser(name, email string) {
user := &entity.User{
Name: name,
Email: email,
}
// 抽象化されたCreateを呼び出す
u.userDataAccess.Create(user)
}
Mock化できるか確認
func main() {
// どちらをDIするかでUsecaseが実際に外部アクセスするかが変わる
dataAccess := UserDataAccess{} // 実際に外部アクセスする機能
dataAccess := UserDataAccessMock{} // 外部アクセスのMock
// UseCaseに外部アクセス機能をDI
usecaseInteractor := UserUsecaseInteractor{
userDataAccess: dataAccess,
}
usecaseInteractor.Create(
// 省略
)
}
3. UI独立
UI独立にはクラス図のPresenter, OutputData, View, ViewModelが関わっています。
UI(View)独自の情報をビジネスルール(UseCaseInteractor)が扱わないようにすることで、ビジネスルールがUIに依存しなくなり、UI独立を満たすことができます。
そのためにPresenterはUseCaseInteractorから受け取った情報(OutputData)をViewが必要とする情報(ViewModel)に変換してからViewに渡スようにします。
実装例
多くのWebフレームワークではMVC2が採用されており、ControllerからViewに情報を渡す必要があります。
そのため、Presenterはなくなり、Controllerがその役割を担うことになります。
type UserController struct {
createUser CreateUserInputBoundary
}
// Userを作成するコントローラーのメソッド
// ctx: フレームワークから受け取る情報
func (u *UserController) Post(ctx *gin.Context) {
// ビジネスルールが必要とする形に変換
inputData := NewCreateUserInputData(ctx.PostForm("name"), ctx.PostForm("email"))
// ビジネスルールに渡す
res, err := u.createUser.Exec(inputData)
/* ここからは本来Presenterの役目 */
// ビジネスルールの結果をViewが必要とする形に変換
statusCode, outputData := NewUserOutputData(res, err)
// Viewに渡す
c.JSON(statusCode, outputData)
}
OutputDataの構造体を用意しないパターン
UseCaseInteractorの戻り値がView独自の情報でなければ、OutputDataという構造体を用意しなくてもフレームワーク独立を満たすことができます。
この場合、クラス図はこのようになります
4. データベース独立
2.テスト可能 で前述した通り、データベース等にアクセスする機能はDataAccessとしてビジネスルールから分離されており、Mockに置き換えることができます。
Mockに置き換えるのと同様に、MongoアクセスするDataAccessをBigTableにアクセスするDataAccessに交換することができます。
実装例
UserデータにアクセスするDataAccessのインターフェースを定義
type UserDataAccessInterface interface {
Create(user *entity.User) error
}
インターフェースを満たすDataAccessを定義
Mongoにアクセスするものと、BigTableにアクセスするものを定義
// Mongoにアクセスする機能
type UserDataAccessMongo struct {
// 省略
}
// アクセスしてUserを作成する
func (*UserDataAccessMongo) Create(user *entity.User) error {
// 省略
}
// BigTableにアクセスする機能
type UserDataAccessBigTable struct {
// 省略
}
// アクセスしてUserを作成する
func (*UserDataAccessBigTable) Create(user *entity.User) error {
// 省略
}
UseCaseInteractorの定義
抽象化されたUserデータにアクセスする機能を呼び出すことで、ビジネスルールはデータベースに依存しない
// ビジネスルール(UseCaseInteractor)
type CreateUserUseCaseInteractor struct {
抽象化されたUserデータにアクセスする機能
userDataAccess UserDataAccessInterface
}
// Userを作成するビジネスルール
func (u *CreateUserUseCaseInteractor) Exec(name, email string) {
user := &entity.User{
Name: name,
Email: email,
}
// 抽象化されたCreateを呼び出す
u.userDataAccess.Create(user)
}
5. 外部機能独立
フレームワーク独立、UI独立、データベース独立が達成できていれば、外部機能独立を達成できています。