청결 건물, 2년 후
이 구조를 이용하여 Codenation의 제품을 개발하여 우리는 경험을 얻었고 문제를 해결했다.우리는 이러한 경험을 보도하기 위해 몇 가지 댓글을 썼다.
Choosing Clean Architecture was the best technical decision we made!
이 글에서 나는 repository을 공유하고 싶은데, 그 중에서 새로운 예시가 실현되었다.이것은 코드와 디렉터리 조직의 개선된 업데이트이며, 이 구조를 실현하고자 하는 사람들에게는 더욱 완전한 예이다.
다음 주제에서, 나는 모든 디렉터리의 의미를 설명할 것이다.
솔리드 레이어
우리 구조의 맨 안쪽부터 시작합시다.
기준 Uncle Bob's post:
Entities encapsulate Enterprise wide business rules. An entity can be an object with methods, or it can be a set of data structures and functions. It doesn’t matter so long as the entities could be used by many different applications in the enterprise.
구조는 다음과 같습니다.
이 가방에서 우리는 실체와 각자의 단원 테스트를 정의했다.예를 들어, 솔리드
user
:package entity
import (
"time"
"golang.org/x/crypto/bcrypt"
)
//User data
type User struct {
ID ID
Email string
Password string
FirstName string
LastName string
CreatedAt time.Time
UpdatedAt time.Time
Books []ID
}
//NewUser create a new user
func NewUser(email, password, firstName, lastName string) (*User, error) {
u := &User{
ID: NewID(),
Email: email,
FirstName: firstName,
LastName: lastName,
CreatedAt: time.Now(),
}
pwd, err := generatePassword(password)
if err != nil {
return nil, err
}
u.Password = pwd
err = u.Validate()
if err != nil {
return nil, ErrInvalidEntity
}
return u, nil
}
//AddBook add a book
func (u *User) AddBook(id ID) error {
_, err := u.GetBook(id)
if err == nil {
return ErrBookAlreadyBorrowed
}
u.Books = append(u.Books, id)
return nil
}
//RemoveBook remove a book
func (u *User) RemoveBook(id ID) error {
for i, j := range u.Books {
if j == id {
u.Books = append(u.Books[:i], u.Books[i+1:]...)
return nil
}
}
return ErrNotFound
}
//GetBook get a book
func (u *User) GetBook(id ID) (ID, error) {
for _, v := range u.Books {
if v == id {
return id, nil
}
}
return id, ErrNotFound
}
//Validate validate data
func (u *User) Validate() error {
if u.Email == "" || u.FirstName == "" || u.LastName == "" || u.Password == "" {
return ErrInvalidEntity
}
return nil
}
//ValidatePassword validate user password
func (u *User) ValidatePassword(p string) error {
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(p))
if err != nil {
return err
}
return nil
}
func generatePassword(raw string) (string, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(raw), 10)
if err != nil {
return "", err
}
return string(hash), nil
}
용례층
밥 아저씨의 말에 의하면
The software in this layer contains application specific business rules. It encapsulates and implements all of the use cases of the system
구조는 다음과 같습니다.
usecase
의 가방에서 우리는 제품의 기타 업무 규칙을 실현했다.예를 들어, 파일
usecase\loan\service.go
:package loan
import (
"github.com/eminetto/clean-architecture-go-v2/entity"
"github.com/eminetto/clean-architecture-go-v2/usecase/book"
"github.com/eminetto/clean-architecture-go-v2/usecase/user"
)
//Service loan usecase
type Service struct {
userService user.UseCase
bookService book.UseCase
}
//NewService create new use case
func NewService(u user.UseCase, b book.UseCase) *Service {
return &Service{
userService: u,
bookService: b,
}
}
//Borrow borrow a book to an user
func (s *Service) Borrow(u *entity.User, b *entity.Book) error {
u, err := s.userService.GetUser(u.ID)
if err != nil {
return err
}
b, err = s.bookService.GetBook(b.ID)
if err != nil {
return err
}
if b.Quantity <= 0 {
return entity.ErrNotEnoughBooks
}
err = u.AddBook(b.ID)
if err != nil {
return err
}
err = s.userService.UpdateUser(u)
if err != nil {
return err
}
b.Quantity--
err = s.bookService.UpdateBook(b)
if err != nil {
return err
}
return nil
}
//Return return a book
func (s *Service) Return(b *entity.Book) error {
b, err := s.bookService.GetBook(b.ID)
if err != nil {
return err
}
all, err := s.userService.ListUsers()
if err != nil {
return err
}
borrowed := false
var borrowedBy entity.ID
for _, u := range all {
_, err := u.GetBook(b.ID)
if err != nil {
continue
}
borrowed = true
borrowedBy = u.ID
break
}
if !borrowed {
return entity.ErrBookNotBorrowed
}
u, err := s.userService.GetUser(borrowedBy)
if err != nil {
return err
}
err = u.RemoveBook(b.ID)
if err != nil {
return err
}
err = s.userService.UpdateUser(u)
if err != nil {
return err
}
b.Quantity++
err = s.bookService.UpdateBook(b)
if err != nil {
return err
}
return nil
}
우리는 mocks
에서 생성된 Gomock
도 발견했다. 예를 들어 post에서 말한 바와 같다.구조의 다른 층은 테스트 기간에 이 시뮬레이션을 사용할 것이다.프레임 및 구동층
밥 아저씨의 말에 의하면
The outermost layer is generally composed of frameworks and tools such as the Database, the Web Framework, etc.This layer is where all the details go.
예를 들어, 파일
infrastructure/repository/user_mysql.go
에서 MySQL에서 인터페이스 Repository
을 구현했습니다.만약 우리가 다른 데이터베이스로 전환해야 한다면, 우리는 이곳에서 새로운 실현을 만들 것이다.인터페이스 어댑터 레이어
이 층의 코드는 데이터를 실체와 외부 에이전트 (예를 들어 데이터베이스, 웹 등) 용례로 조정하고 변환합니다.
이 응용 프로그램에는
UseCases
에 접근할 수 있는 두 가지 방법이 있다.첫 번째는 API
을 통해, 두 번째는 명령행 응용 프로그램(CLI
)을 사용하는 것이다.CLI
의 구조는 매우 간단하다.도메인 패키지를 사용하여 도서 검색을 수행합니다.
dataSourceName := fmt.Sprintf("%s:%s@tcp(%s:3306)/%s?parseTime=true", config.DB_USER, config.DB_PASSWORD, config.DB_HOST, config.DB_DATABASE)
db, err := sql.Open("mysql", dataSourceName)
if err != nil {
log.Fatal(err.Error())
}
defer db.Close()
repo := repository.NewBookMySQL(db)
service := book.NewService(repo)
all, err := service.SearchBooks(query)
if err != nil {
log.Fatal(err)
}
for _, j := range all {
fmt.Printf("%s %s \n", j.Title, j.Author)
}
위의 예시에서 config
패키지의 사용을 볼 수 있습니다.아래에서 구조를 볼 수 있습니다. 자세한 내용은 post을 참조하십시오.API
은 구조가 더욱 복잡하고 세 개의 가방이 있다. handler
, presenter
과 middleware
이다.handler
패키지 처리 HTTP
requests
과 responses
, 그리고 usecases
의 기존 업무 규칙을 사용합니다.presenters
은 response
에서 생성된 handlers
데이터를 포맷합니다.솔리드
User
:type User struct {
ID ID
Email string
Password string
FirstName string
LastName string
CreatedAt time.Time
UpdatedAt time.Time
Books []ID
}
다음과 같이 변환할 수 있습니다.type User struct {
ID entity.ID `json:"id"`
Email string `json:"email"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
}
이것은 우리로 하여금 API
을 통해 실체를 어떻게 교부하는지 제어할 수 있게 한다.API
의 마지막 가방에서 middlewares
을 찾았습니다. 몇 개의 endpoints
이 사용했습니다.지원 패키지
암호화, 로그 기록, 파일 처리 등 흔히 볼 수 있는 기능을 제공하는 패키지입니다. 이 기능들은 우리의 응용 프로그램 영역에 속하지 않고 모든 층에서 사용할 수 있습니다.심지어 다른 응용 프로그램도 이 패키지를 가져오고 사용할 수 있다.
README.md은 컴파일 설명과 사용 예시와 같은 더 많은 세부 사항을 포함한다.
이 글의 목표는 이 구조에 대한 건의를 강화하고 코드에 대한 피드백을 받는 것이다.
가장 좋아하는 프로그래밍 언어에서 이 구조를 사용하는 방법을 배우고 싶다면, 이 저장소를 학습 예로 사용할 수 있습니다.이렇게 하면 우리는 서로 다른 언어로 서로 다른 기능을 실현하여 비교하기 편리하게 할 수 있다.
특히 제 친구에게 감사드립니다. 그는 텍스트와 코드에 대해 좋은 피드백을 해 주었습니다.
Reference
이 문제에 관하여(청결 건물, 2년 후), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/eminetto/clean-architecture-2-years-later-4een텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)