Skip to content

Commit 546a069

Browse files
authored
Merge pull request #891 from devlights/add-hexagonal_architecture-example
2 parents b41c4c3 + b1648db commit 546a069

File tree

2 files changed

+248
-0
lines changed

2 files changed

+248
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# https://taskfile.dev
2+
3+
version: '3'
4+
5+
tasks:
6+
default:
7+
cmds:
8+
- go run main.go
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
/*
2+
ヘキサゴナルアーキテクチャのサンプル
3+
4+
adapter -> port <- usecase -> domain
5+
6+
- adapter
7+
- FixedAmountAdapter (Inbound)
8+
- ConsoleAdapter (Outbound)
9+
10+
- port
11+
- InputPort
12+
- OutputPort
13+
14+
- usecase
15+
- YenCalculator
16+
17+
- domain
18+
- Yen
19+
*/
20+
package main
21+
22+
import (
23+
"errors"
24+
"fmt"
25+
"io"
26+
"os"
27+
)
28+
29+
// ------------------------------------
30+
// ドメインエラー
31+
// ------------------------------------
32+
type DomainError struct {
33+
Message string
34+
Cause error
35+
}
36+
37+
func (e *DomainError) Error() string {
38+
if e.Cause != nil {
39+
return fmt.Sprintf("%s: %v", e.Message, e.Cause)
40+
}
41+
return e.Message
42+
}
43+
44+
func NewDomainError(msg string, cause error) *DomainError {
45+
return &DomainError{
46+
Message: msg,
47+
Cause: cause,
48+
}
49+
}
50+
51+
// ------------------------------------
52+
// エンティティ
53+
// ------------------------------------
54+
type Yen struct {
55+
Amount int64
56+
TaxRate float64
57+
}
58+
59+
func (me Yen) WithTax() (int64, error) {
60+
if me.Amount < 0 {
61+
return 0, NewDomainError("金額は0以上である必要があります", nil)
62+
}
63+
64+
if me.TaxRate <= 0 || me.TaxRate >= 100 {
65+
return 0, NewDomainError("税率は0より大きく100未満である必要があります", nil)
66+
}
67+
68+
v := float64(me.Amount) * me.TaxRate
69+
return int64(v), nil
70+
}
71+
72+
// ------------------------------------
73+
// ユースケース (ビジネスロジック)
74+
// ------------------------------------
75+
type YenCalculator struct {
76+
in InputPort
77+
out OutputPort
78+
}
79+
80+
func NewYenCalculator(in InputPort, out OutputPort) *YenCalculator {
81+
return &YenCalculator{in, out}
82+
}
83+
84+
func (me *YenCalculator) Calc() error {
85+
v, err := me.in.Value()
86+
if err != nil {
87+
return fmt.Errorf("入力値の取得に失敗しました: %w", err)
88+
}
89+
90+
y := Yen{v, 10.0}
91+
result, err := y.WithTax()
92+
if err != nil {
93+
return fmt.Errorf("税込計算に失敗しました: %w", err)
94+
}
95+
96+
if err := me.out.Output(v, result); err != nil {
97+
return fmt.Errorf("出力処理に失敗しました: %w", err)
98+
}
99+
100+
return nil
101+
}
102+
103+
// ------------------------------------
104+
// ポート
105+
// ------------------------------------
106+
107+
// ------------------------------------
108+
// Inbound
109+
type InputPort interface {
110+
Value() (int64, error)
111+
}
112+
113+
// ------------------------------------
114+
// Outbound
115+
type OutputPort interface {
116+
Output(before, after int64) error
117+
}
118+
119+
// ------------------------------------
120+
// アダプタ
121+
// ------------------------------------
122+
123+
// ------------------------------------
124+
// プライマリアダプタ
125+
type FixedAmountAdapter struct{}
126+
127+
func (me *FixedAmountAdapter) Value() (int64, error) {
128+
return int64(100), nil
129+
}
130+
131+
type SquaredAmountAdapter struct {
132+
Amount int
133+
}
134+
135+
func (me *SquaredAmountAdapter) Value() (int64, error) {
136+
if me.Amount < 0 {
137+
return 0, errors.New("入力値は0以上である必要があります")
138+
}
139+
v := me.Amount * me.Amount
140+
return int64(v), nil
141+
}
142+
143+
// 入力エラーをシミュレートするアダプター
144+
type ErrorInputAdapter struct{}
145+
146+
func (me *ErrorInputAdapter) Value() (int64, error) {
147+
return 0, errors.New("入力ソースからの読み取りに失敗しました")
148+
}
149+
150+
// ------------------------------------
151+
// セカンダリアダプタ
152+
type ConsoleAdapter struct{}
153+
154+
func (me *ConsoleAdapter) Output(before, after int64) error {
155+
_, err := fmt.Printf("b:%d\ta:%d\n", before, after)
156+
return err
157+
}
158+
159+
type FileOutAdapter struct {
160+
Writer io.Writer
161+
}
162+
163+
func (me *FileOutAdapter) Output(before, after int64) error {
164+
_, err := fmt.Fprintf(me.Writer, "b:%d\ta:%d\n", before, after)
165+
return err
166+
}
167+
168+
// ------------------------------------
169+
// ユーティリティ
170+
// ------------------------------------
171+
func handleError(err error) {
172+
if err != nil {
173+
fmt.Fprintf(os.Stderr, "エラーが発生しました: %v\n", err)
174+
175+
// ドメインエラーとその他のエラーで処理を分ける
176+
var domainErr *DomainError
177+
if errors.As(err, &domainErr) {
178+
fmt.Fprintf(os.Stderr, "ドメインエラー: %s\n", domainErr.Message)
179+
}
180+
}
181+
}
182+
183+
func main() {
184+
var (
185+
in InputPort // Inbound Port
186+
out OutputPort // Outbound Port
187+
uc *YenCalculator // Usecase
188+
)
189+
190+
// 1: 基本パターン
191+
in = &FixedAmountAdapter{}
192+
out = &ConsoleAdapter{}
193+
uc = NewYenCalculator(in, out)
194+
195+
if err := uc.Calc(); err != nil {
196+
handleError(err)
197+
} else {
198+
fmt.Println("計算1が正常に完了しました")
199+
}
200+
201+
// 2: 出力アダプターを変更
202+
out = &FileOutAdapter{Writer: os.Stderr}
203+
uc = NewYenCalculator(in, out)
204+
205+
if err := uc.Calc(); err != nil {
206+
handleError(err)
207+
} else {
208+
fmt.Println("計算2が正常に完了しました")
209+
}
210+
211+
// 3: 入力アダプターを変更
212+
in = &SquaredAmountAdapter{Amount: 20}
213+
uc = NewYenCalculator(in, out)
214+
215+
if err := uc.Calc(); err != nil {
216+
handleError(err)
217+
} else {
218+
fmt.Println("計算3が正常に完了しました")
219+
}
220+
221+
// 4: エラーケース - 入力エラー
222+
in = &ErrorInputAdapter{}
223+
uc = NewYenCalculator(in, out)
224+
225+
if err := uc.Calc(); err != nil {
226+
handleError(err)
227+
} else {
228+
fmt.Println("計算4が正常に完了しました")
229+
}
230+
231+
// 5: エラーケース - ドメインエラー(負の入力値)
232+
in = &SquaredAmountAdapter{Amount: -10}
233+
uc = NewYenCalculator(in, out)
234+
235+
if err := uc.Calc(); err != nil {
236+
handleError(err)
237+
} else {
238+
fmt.Println("計算5が正常に完了しました")
239+
}
240+
}

0 commit comments

Comments
 (0)