---
title: "Tokenization 機制設計 - Clean Architecture in Go"
date: 2025-03-07T00:00:00+08:00
publishDate: 2025-03-07T00:00:00+08:00
lastmod: 2024-10-07T20:05:43+08:00
tags: ["Golang","Clean Architecture","架構","經驗"]
series: "clean-architecture-in-go"
toc: true
permalink: "https://blog.aotoki.me/posts/2025/03/07/clean-architecture-in-go-tokenization/"
language: "zh-tw"
---


訂單的建立和查詢已經完成，然而下單者的名字仍然被明碼儲存在我們系統之中，我們希望針對個資做更好的保護，因此決定導入代號化（Tokenization）的機制，將原本儲存的名字已代號（Token）儲存，並且將真實的資料另外加密。

## Tokenization

[Tokenization](https://en.wikipedia.org/wiki/Tokenization_(data_security)) 是一種資訊安全的技術，比起直接儲存明碼，或者在某個資料庫欄位上進行加密，可以有更好的安全性，以及更容易處理個資的刪除。

原理上並不複雜，我們只需要另外建立一個資料表（如：`tokens`）並且儲存代號以及對應的資料即可，在金流服務中，我們串接信用卡進行扣款時，銀行就會將卡號轉換成一個代號，外部的服務透過這個代號呼叫，就可以避免直接儲存明碼的信用卡卡號。

當我們將這個技術用在隱私相關的情境時，就可以達到去識別化的效果，在資料處理上都是某個代號，因此平常無法跟某個個資產生關聯，那麼就只需要保護好管理代號的資料表即可，用來避免個資散落在系統不同的地方。

## 設計功能{#design-feature}

我們原有的建立、查詢訂單介面基本上已經確定下來，這表示我們加入代號化的處理不能破壞這個機制，那們我們該如何修改哪？

現有的系統中，我們有一個這樣的實體（Entity）

```go
type Order struct {
	id    string
	name  string
	items []Item
	state string
}
```

現在，原本 `name` 儲存的是 `Aotoki` 的明碼，要轉換成像是這樣的代號格式 `v1:de5c5369-19f2-4eaf-b5f0-7765619037de` 在過往 MVC 的設計，我們可能會考慮以下選項

* 在 Controller 收到請求時處理
* 在 Model 插入額外的 Hook 處理

如果依照 Clean Architecture 的思考方式，在 Controller 處理是比較合理的，因為對 Model 來說，職責是管理狀態，要保存怎樣的資訊並不是關注的重點。

那麼，在我們現在的設計中，Use Case（使用案例）就很關鍵，因為我們可以在裡面描述一個「流程」中間具備一個代號化的機制。

## 擴充功能{#extend-feature}

要在 Use Case 中插入這個機制，實際上並不複雜，雖然我們會有其他問題需要考慮，然而在現階段可以先無視像資料庫的交易（Transaction）是否成功的問題，因此可以預期原本的 Place Order Use Case 會有這樣的調整。

```go
// internal/usecase/place_order.go

// ...
func (u *PlaceOrder) Execute(ctx context.Context, input *PlaceOrderInput) (*PlaceOrderOutput, error) {
	nameToken := tokens.New(
		uuid.NewString(),
		input.Name
	)

	err := u.tokens.Save(token)
	// ...

	order := orders.New(
		uuid.NewString(),
		nameToken.String(),
	)

	// ...

	out := &PlaceOrderOutput{
		Id:    order.Id(),
		Name:  nameToken.Raw(),
		Items: []PlaceOrderItem{},
	}

	// ...

	return out, nil
}
```

跟原本的版本相比，我們建立了一個叫做 `nameToken` 的實體，並且將它保存起來。同時將原本訂單的 `name` 改為 `nameToken.String()` 傳入，在輸出的部分則用 `nameToken.Raw()` 顯示原始的內容，讓 API 行為保持一致。

實際上修改並不複雜，我們接下來就開始著手重構這個功能讓個資保護的機制變得更完善。

