代码结构分层

在开始记录实践之前,建议先阅读一下两篇文章:
The Clean Architecture
Trying Clean Architecture on Golang

Clean Architecture基本概念

Each of these architectures produce systems that are:

  1. Independent of Frameworks. The architecture does not depend on the existence of some library of feature laden software. This allows you to use such frameworks as tools, rather than having to cram your system into their limited constraints.
  2. Testable. The business rules can be tested without the UI, Database, Web Server, or any other external element.
  3. Independent of UI. The UI can change easily, without changing the rest of the system. A Web UI could be replaced with a console UI, for example, without changing the business rules.
  4. Independent of Database. You can swap out Oracle or SQL Server, for Mongo, BigTable, CouchDB, or something else. Your business rules are not bound to the database.
  5. Independent of any external agency. In fact your business rules simply don’t know anything at all about the outside world.

An attempt:
CleanArchitecture

The Dependency Rule

Entities: 封装了企业维度的business rules. Entity可以是an object with methods, 或者是a set of data structures and functions. 如果是简单应用,entity则是应用的business objects。他们应该是最不可能随外部变化的。
Use Cases: 包含了application specific的business rules。封装并实施all the use cases of the system.这些use cases orchestrate the flow of data to and from the entities, and direct those entities to use their enterprise wide business rules to achieve the goals of the use case.
Interface Adapters: 是a set of adapters用来把数据从use cases和entities层的格式转换到external agency(DB or web)。这个层会完整包含MVC结构。同样,这层会把从use cases和entities层的数据形式转换到持久层framework, 比方数据库。在此层往内的代码无需知道数据库。如果是SQL数据库,则所有SQL必须受到此层约束,尤其是和数据库相关的部分。
Frameworks & Drivers: 最外层一般由框架和工具组成,比方数据库,网络架构等。一般只放些与外界沟通的胶水代码。

Crossing boundaries

At the lower right of the diagram is an example of how we cross the circle boundaries. It shows the Controllers and Presenters communicating with the Use Cases in the next layer. Note the flow of control. It begins in the controller, moves through the use case, and then winds up executing in the presenter. Note also the source code dependencies. Each one of them points inwards towards the use cases.

We usually resolve this apparent contradiction by using the Dependency Inversion Principle. In a language like Java, for example, we would arrange interfaces and inheritance relationships such that the source code dependencies oppose the flow of control at just the right points across the boundary.

Dependency Inversion Principle
记得使用Dependency Inversion Principle,即:

  1. High-level modules should not import anything from low-level modules. Both should depend on abstractions (e.g., interfaces).
  2. Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.
    In a language like Java, for example, we would arrange interfaces and inheritance relationships such that the source code dependencies oppose the flow of control at just the right points across the boundary.
    screenshot-20220613-215721

For example, consider that the use case needs to call the presenter. However, this call must not be direct because that would violate The Dependency Rule: No name in an outer circle can be mentioned by an inner circle. So we have the use case call an interface (Shown here as Use Case Output Port) in the inner circle, and have the presenter in the outer circle implement it.

The same technique is used to cross all the boundaries in the architectures. We take advantage of dynamic polymorphism to create source code dependencies that oppose the flow of control so that we can conform to The Dependency Rule no matter what direction the flow of control is going in.

Golang中的对应

Entities-->Models: 存储对象结构与方法。Same as Entities, will used in all layer. This layer, will store any Object’s Struct and its method. Any entities, or model will stored here.
举例:

import "time"

type Article struct {
	ID        int64     `json:"id"`
	Title     string    `json:"title"`
	Content   string    `json:"content"`
	UpdatedAt time.Time `json:"updated_at"`
	CreatedAt time.Time `json:"created_at"`
}

Use Cases: 处理业务流程。This layer will act as the business process handler. Any process will handled here. This layer will decide, which repository layer will use. And have responsibility to provide data to serve into delivery. Process the data doing calculation or anything will done here.
Usecase layer will accept any input from Delivery layer, that already sanitized, then process the input could be storing into DB , or Fetching from DB ,etc.
This Usecase layer will depends to Repository Layer

Delivery: 扮演presenter,API接口等。This layer will act as the presenter. Decide how the data will presented. Could be as REST API, or HTML File, or gRPC whatever the delivery type.
This layer also will accept the input from user. Sanitize the input and sent it to Usecase layer.
For my sample project, I’m using REST API as the delivery method.
Client will call the resource endpoint over network, and the Delivery layer will get the input or request, and sent it to Usecase Layer.
This layer will depends to Usecase Layer.

Repository: 纯数据库操作。Repository will store any Database handler. Querying, or Creating/ Inserting into any database will stored here. This layer will act for CRUD to database only. No business process happen here. Only plain function to Database.
This layer also have responsibility to choose what DB will used in Application. Could be Mysql, MongoDB, MariaDB, Postgresql whatever, will decided here.
If using ORM, this layer will control the input, and give it directly to ORM services.
If calling microservices, will handled here. Create HTTP Request to other services, and sanitize the data. This layer, must fully act as a repository. Handle all data input - output no specific logic happen.
This Repository layer will depends to Connected DB , or other microservices if exists.

Communications Between Layers

Except Models, each layer will communicate through inteface. For example, Usecase layer need the Repository layer, so how they communicate? Repository will provide an interface to be their contract and communication.

References:
https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
https://hackernoon.com/golang-clean-archithecture-efd6d7c43047
https://www.cnblogs.com/gaochundong/p/dependency_inversion_principle.html
https://www.jianshu.com/p/272dc047d527

Subscribe to 隅

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe