
在 Place Order 實作 Token 機制 - Clean Architecture in Go
我們已經對於 Tokenization 機制的設計有大致的方向,那麼就可以開始來對 Place Order 的流程進行調整,將原本直接把客戶名稱儲存到 Order 改為先向 Token 機制要求產生一個代號,再作為原本的替代儲存進去。
Token Entity
Token 的實作並不複雜,我們只需要處理三項資訊。
- 代號
- 版本
- 資料
因此可以像這樣實作
1// internal/entity/tokens/token.go
2package tokens
3
4// ...
5
6const (
7 CurrentVersion = "v1"
8)
9
10type Token struct {
11 id string
12 data []byte
13 version string
14}
15
16func New(id string) *Token {
17 return &Token{
18 id: id,
19 version: CurrentVersion,
20 }
21}
22
23func (t *Token) Id() string {
24 return t.id
25}
26
27func (t *Token) Data() []byte {
28 return t.data
29}
30
31func (t *Token) Version() string {
32 return t.version
33}
34
35func (t *Token) SetData(data []byte) {
36 t.data = data
37}
38
39func (t Token) String() string {
40 return t.version + ":" + t.id
41}
不會太過複雜,主要就是將必要的資訊進行簡單的封裝,有這樣的機制就非常足夠我們在後續的實作中使用。
Place Order
有了 Token 後,我們就可以更新 Place Order Use Case 的流程,在原本的流程中插入一段 nameToken
的處理機制,關於 TokenRepository
我們會在後續討論相應的實作。
1// internal/usecase/place_order.go
2
3type PlaceOrder struct {
4 orders OrderRepository
5 tokens TokenRepository
6}
7
8func NewPlaceOrder(orders OrderRepository, tokens TokenRepository) *PlaceOrder {
9 return &PlaceOrder{
10 orders: orders,
11 tokens: tokens,
12 }
13}
14
15func (u *PlaceOrder) Execute(ctx context.Context, input *PlaceOrderInput) (*PlaceOrderOutput, error) {
16 nameToken := tokens.New(uuid.NewString())
17 nameToken.SetData([]byte(input.Name))
18
19 if err := u.tokens.Save(ctx, nameToken); err != nil {
20 return nil, err
21 }
22
23 order := orders.New(
24 uuid.NewString(),
25 nameToken.String(),
26 )
27
28 for _, item := range input.Items {
29 err := order.AddItem(item.Name, item.Quantity, item.UnitPrice)
30 if err != nil {
31 return nil, err
32 }
33 }
34
35 if err := u.orders.Save(ctx, order); err != nil {
36 return nil, err
37 }
38
39 out := &PlaceOrderOutput{
40 Id: order.Id(),
41 Name: string(nameToken.Data()),
42 Items: []PlaceOrderItem{},
43 }
44
45 for _, item := range order.Items() {
46 out.Items = append(out.Items, PlaceOrderItem{
47 Name: item.Name(),
48 Quantity: item.Quantity(),
49 UnitPrice: item.UnitPrice(),
50 })
51 }
52
53 return out, nil
54}
大部分的內容跟我們在設計 Order 的實作是差不多的,只是在這裡我們多出了 nameToken
的處理,產生了一個 Token
用於替代原本明碼的顧客名稱。
TokenRepository
處理上也跟 OrderRepository 上類似,我們在 internal/usecase
描述需要的介面,再由 internal/repository
來實作對應的機制。
1// internal/usecase/repository.go
2
3// ...
4
5type TokenRepository interface {
6 Save(ctx context.Context, token *tokens.Token) error
7}
在開發初期,我們可以使用 InMemoryTokenRepository
來對應撰寫初期測試所需的版本,因此跟 InMemoryOrderRepository
相同快速的實作一個類似的實作。
1// internal/repository/in_memory_tokens.go
2
3// ...
4
5type InMemoryTokenSchema struct {
6 Id string
7 Data []byte
8 Version string
9}
10
11type InMemoryTokenRepository struct {
12 tokens map[string]InMemoryTokenSchema
13}
14
15func NewInMemoryTokenRepository() *InMemoryTokenRepository {
16 return &InMemoryTokenRepository{
17 tokens: map[string]InMemoryTokenSchema{},
18 }
19}
20
21func (r *InMemoryTokenRepository) Save(ctx context.Context, token *tokens.Token) error {
22 r.tokens[token.Id()] = InMemoryTokenSchema{
23 Id: token.Id(),
24 Data: token.Data(),
25 Version: token.Version(),
26 }
27
28 return nil
29}
因為我們在前期打下的基礎,在要進行擴充的時候並不會耗費太多力氣。受限於文章篇幅,我們使用的案例並不會太過複雜,因此現階段即使將 UseCase 的實作放在 Controller 中也不會有太大的差異,在後續加入像是 gRPC、真實的資料庫整合案例後,就可以逐步看出 Clean Architecture 對這幾大類物件的區分,是怎麼幫助我們更快的擴充系統能力。
如果對這篇文章有興趣,可以透過以下連結繼續閱讀這系列的其他文章。
- 連載介紹 - Clean Architecture in Go
- 目標設定 - Clean Architecture in Go
- wire 的依賴注入 - Clean Architecture in Go
- 案例說明 - Clean Architecture in Go
- 操作介面設計 - Clean Architecture in Go
- Place Order 實作 Controller 部分 - Clean Architecture in Go
- Place Order 實作 Entity 部分 - Clean Architecture in Go
- Place Order 實作 Repository 部分 - Clean Architecture in Go
- Lookup Order 功能 - Clean Architecture in Go
- Tokenization 機制設計 - Clean Architecture in Go
- 在 Place Order 實作 Token 機制 - Clean Architecture in Go
- 在 Lookup Order 實作 Token 機制 - Clean Architecture in Go
- Token 內容加密 - Clean Architecture in Go