Cách cấu trúc một ứng dụng Go, được kiến ​​trúc theo kiến ​​trúc sạch


9

Tôi đang cố gắng xây dựng một dự án bằng kiến ​​trúc sạch, như được mô tả ở đây . Tôi tìm thấy một bài viết tuyệt vời về cách làm điều này trong Go .

Ví dụ này rất đơn giản và tác giả đặt mã của họ vào các gói được đặt tên dựa trên lớp mà họ đang ở. Tôi thích ý tưởng của chú Bob rằng kiến ​​trúc của một ứng dụng nên truyền đạt rõ ràng ý định của nó . Vì vậy, tôi muốn ứng dụng của mình có các gói cấp cao nhất dựa trên các vùng miền. Vì vậy, cấu trúc tập tin của tôi sẽ trông giống như thế này:

/Customers
    /domain.go
    /interactor.go
    /interface.go
    /repository.go
/... the same for other domain areas

Vấn đề với điều này là nhiều lớp chia sẻ cùng một gói. Vì vậy, không rõ ràng khi quy tắc phụ thuộc bị vi phạm, bởi vì bạn không có nhập khẩu cho thấy những gì phụ thuộc vào những gì.

Tôi đến từ nền Python, nơi điều này sẽ không gây ra nhiều vấn đề, bởi vì bạn có thể nhập các tệp riêng lẻ, do đó customers.interactorcó thể nhập customers.domain.

Chúng ta có thể đạt được một cái gì đó tương tự trong gO bằng cách lồng các gói, để gói khách hàng chứa gói tên miền và gói tương tác, v.v. Điều này cảm thấy lộn xộn, và các gói có tên giống hệt nhau có thể gây khó chịu để đối phó.

Một lựa chọn khác là tạo nhiều gói cho mỗi vùng miền. Một cái gọi là customer_domain, một cái gọi là customer_interactor, v.v. Nhưng điều này cũng cảm thấy bẩn. Nó không phù hợp với hướng dẫn đặt tên gói của Go và có vẻ như tất cả các gói riêng biệt này nên được nhóm lại bằng cách nào đó, vì tên của chúng có tiền tố chung.

Vì vậy, những gì sẽ là một bố trí tập tin tốt cho việc này?


Là một người đã chịu đựng một kiến ​​trúc tổ chức các gói độc quyền theo cấp độ kiến ​​trúc, hãy để tôi nói lên sự hỗ trợ của tôi đối với các gói dựa trên tính năng . Tôi là tất cả để truyền đạt ý định nhưng đừng bắt tôi phải bò qua mọi ngóc ngách khó hiểu chỉ để thêm một tính năng mới.
candied_orange

Theo ý định, tôi có nghĩa là các gói nên truyền đạt những gì ứng dụng nói về. Vì vậy, nếu bạn đang xây dựng một ứng dụng thư viện, bạn sẽ có một gói sách, gói cho vay, v.v.
bigblind

Câu trả lời:


5

Có một vài giải pháp cho vấn đề này:

  1. Tách gói
  2. Đánh giá phân tích
  3. Phân tích tĩnh
  4. Phân tích thời gian chạy

Mỗi người có ưu / nhược điểm của họ.

Tách gói

Đây là cách dễ nhất mà không yêu cầu xây dựng thêm bất cứ điều gì. Nó có hai hương vị:

// /app/user/model/model.go
package usermodel
type User struct {}

// /app/user/controller/controller.go
package usercontroller
import "app/user/model"
type Controller struct {}

Hoặc là:

// /app/model/user.go
package model
type User struct {}

// /app/controller/user.go
package controller
import "app/user/model"

type User struct {}

Điều này tuy nhiên phá vỡ tính toàn vẹn của khái niệm User. Để hiểu hoặc sửa đổi, Userbạn cần phải chạm vào một số gói.

Nhưng, nó có một đặc tính tốt là rõ ràng hơn khi modelnhập khẩu controllervà đối với một số phạm vi, nó được thi hành bởi ngữ nghĩa ngôn ngữ.

Đánh giá phân tích

Nếu ứng dụng không lớn (dưới 30KLOC) và bạn có lập trình viên giỏi, thì thường không cần thiết phải xây dựng bất cứ thứ gì. Tổ chức các cấu trúc dựa trên giá trị sẽ là đủ, ví dụ:

// /app/user/user.go
package user
type User struct {}
type Controller struct {}

Thường thì "vi phạm ràng buộc" rất ít quan trọng hoặc dễ sửa chữa. Nó gây hại cho sự rõ ràng và dễ hiểu - miễn là bạn không để nó ra khỏi tầm tay, bạn không phải lo lắng về điều đó.

Phân tích tĩnh / thời gian chạy

Bạn cũng có thể sử dụng phân tích tĩnh hoặc thời gian chạy để tìm các lỗi này, thông qua các chú thích:

Tĩnh:

// /app/user/user.go
package user

// architecture: model
type User struct {}

// architecture: controller
type Controller struct {}

Năng động:

// /app/user/user.go
package user

import "app/constraint"

var _ = constraint.Model(&User{})
type User struct {}

var _ = constraint.Controller(&Controller{})
type Controller struct {}

// /app/main.go
package main

import "app/constraint"

func init() { constraint.Check() }

Cả tĩnh / động cũng có thể được thực hiện thông qua các trường:

// /app/user/user.go
package user

import "app/constraint"

type User struct {   
    _ constraint.Model
}

type Controller struct {
    _ constraint.Controller
}

Tất nhiên việc tìm kiếm những thứ như vậy trở nên phức tạp hơn.

Các phiên bản khác

Các cách tiếp cận như vậy có thể được sử dụng ở những nơi khác, không chỉ các ràng buộc kiểu, mà còn cả cách đặt tên func, API-s, v.v.

https://play.golang.org/p/4bCOV3tYz7

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.