| name | go-arch-guard |
| description | Use when user explicitly mentions go-arch-guard, architecture guardrails, or architecture_test.go in a Go project. Handles AI-agent-friendly scaffolding, preset configuration, and automation-oriented setup. |
| user_invocable | true |
go-arch-guard
Go ํ๋ก์ ํธ์ ์ํคํ
์ฒ ๊ฐ๋๋ ์ผ์ go test๋ก ์ ์ฉํ๊ณ , AI ์์ด์ ํธ๊ฐ ๋ฐ๋ก ์ค์บํด๋ฉํ๊ณ ์ ์งํ๊ธฐ ์ฌ์ด ํํ๋ก ์๋ดํ๋ค.
Decision Flow
ํ๋ก์ ํธ ์ํ๋ฅผ ํ์ธํ๊ณ ๋ถ๊ธฐํ๋ค.
internal/ ์กด์ฌ ์ฌ๋ถ ํ์ธ
architecture_test.go ์กด์ฌ ์ฌ๋ถ ํ์ธ
์ ํ๋ก์ ํธ (internal/ ์์ AND architecture_test.go ์์) โ Quick Init ์คํ
๊ธฐ์กด ํ๋ก์ ํธ โ Architecture Reference ์ฐธ์กฐํ์ฌ ์์ /๋ฆฌํฉํ ๋ง
Quick Init
Step 1: ํ๋ฆฌ์
์ ํ
| ํ๋ฆฌ์
| ํจ์ | ๋ ์ด์ด ๋ฐฉํฅ |
|---|
| DDD | DDD() (๊ธฐ๋ณธ๊ฐ) | handlerโappโcore/{model,repo,svc},event; infraโcore/repo+core/model+event |
| Clean Architecture | CleanArch() | handlerโusecaseโentity+gateway; infraโgateway+entity |
| Layered | Layered() | handlerโserviceโrepository+model; repositoryโmodel |
| Hexagonal | Hexagonal() | handlerโusecaseโport+domain; adapterโport+domain |
| Modular Monolith | ModularMonolith() | apiโapplicationโcore; infrastructureโcore |
| Consumer/Worker | ConsumerWorker() | workerโserviceโstoreโmodel |
| Batch | Batch() | jobโserviceโstoreโmodel |
| Event-Driven Pipeline | EventPipeline() | commandโaggregateโevent; projectionโreadstore |
Step 2: ์ค์บํด๋ฉ
go get github.com/NamhaeSusan/go-arch-guard
mkdir -p internal/domain internal/orchestration internal/pkg
Step 3: architecture_test.go ์์ฑ
ํ์ผ์ด ์ด๋ฏธ ์กด์ฌํ๋ฉด ๋ฎ์ด์ฐ์ง ์๊ณ ์ ์ ์๊ฒ ํ์ธํ๋ค.
ํจํค์ง๋ช
์ ์ ํจํ Go ํจํค์ง ์๋ณ์์ฌ์ผ ํ๋ค. ์: myapp_test
ํ์ดํ์ด ์๋ module basename(์: go-arch-guard)์ ๊ทธ๋๋ก ์ฐ๋ฉด ์ ๋๋ค.
์ฐ์ scaffold.ArchitectureTest(...)๋ก ํ๋ฆฌ์
๋ณ ํ
ํ๋ฆฟ์ ์์ฑํ๋ ๊ฒ์ ์ฐ์ ํ๋ค.
import "github.com/NamhaeSusan/go-arch-guard/scaffold"
src, err := scaffold.ArchitectureTest(
scaffold.PresetDDD,
scaffold.ArchitectureTestOptions{PackageName: "myapp_test"},
)
if err != nil {
return err
}
๋นํ์ค ํจํค์ง ๋ฃจํธ(packages/, src/ ๋ฑ)๋ฅผ ์ฐ๋ ํ๋ก์ ํธ๋ InternalRoot๋ ํจ๊ป ์ง์ ํ๋ค โ ๊ทธ๋์ผ ์์ฑ๋ analyzer.Load(".", "<root>/...", "cmd/...")๊ฐ ์ค์ ๋ ์ด์์๊ณผ ๋งค์นญ๋๋ค:
src, err := scaffold.ArchitectureTest(
scaffold.PresetDDD,
scaffold.ArchitectureTestOptions{
PackageName: "myapp_test",
InternalRoot: "packages",
},
)
์์ฑ๋ ๊ฒฐ๊ณผ๋ฅผ architecture_test.go์ ์ ์ฅํ๋ค. ์์ฑ ํ
ํ๋ฆฟ์ ๋ด๋ถ์์
core.NewContext(...), presets.{Preset}(), presets.Recommended{Preset}(),
core.Run(...), report.AssertNoViolations(...)๋ฅผ ์ฌ์ฉํ๋ค.
์๋ ์์ฑ ์์๋ ๊ฐ์ ํ๋ฆ์ ๋ฐ๋ฅธ๋ค: analyzer.Load๋ก ํจํค์ง๋ฅผ ๋ก๋ํ๊ณ ,
ํ๋ฆฌ์
์ํคํ
์ฒ๋ก core.NewContext๋ฅผ ๋ง๋ ๋ค, ๊ถ์ฅ ruleset์ core.Run์ ๋๊ธด๋ค.
์ธ๋ถ rule์ ๊ฐ๋ณ๋ก ๋ค๋ค์ผ ํ ๋๋ง core.NewRuleSet(...)์ผ๋ก ์ง์ ์กฐํฉํ๋ค.
rule์ด panic์ ๋ด๋ฉด core.Run์ meta.rule-panic Error violation์ผ๋ก ๋ณํํ๊ณ
๋๋จธ์ง rule ์คํ์ ๊ณ์ํ๋ค.
ํ๋ฆฌ์
๋งคํ:
| ํ๋ฆฌ์
| scaffold ์์ |
|---|
| DDD | scaffold.PresetDDD |
| Clean Architecture | scaffold.PresetCleanArch |
| Layered | scaffold.PresetLayered |
| Hexagonal | scaffold.PresetHexagonal |
| Modular Monolith | scaffold.PresetModularMonolith |
| Consumer/Worker | scaffold.PresetConsumerWorker |
| Batch | scaffold.PresetBatch |
| Event-Driven Pipeline | scaffold.PresetEventPipeline |
{Preset}์ ์ ํํ ํจ์๋ช
์ผ๋ก ์นํ. opts...๊ฐ ์๋ ์ ์ฒด ํ
ํ๋ฆฟ์ README.md Quick Start ์ฐธ์กฐ.
Step 4: ๊ฒ์ฆ
go test -run TestArchitecture -v
๋๋ฉ์ธ์ด ์์ง ์์ผ๋ฏ๋ก ๋ชจ๋ ์ฒดํฌ ํต๊ณผ. ์ดํ ๋๋ฉ์ธ ์ถ๊ฐ ์ ๊ฐ๋๋ ์ผ ์ ์ฉ.
Step 5: ์๋ด
์ค์บํด๋ฉ ์๋ฃ. internal/domain/ ์๋์ ์ฒซ ๋ฒ์งธ ๋๋ฉ์ธ์ ์ถ๊ฐํ์ธ์.
๋ ์ด์ด ๊ตฌ์กฐ๋ ์๋ Architecture Reference๋ฅผ ์ฐธ๊ณ ํ์ธ์.
Architecture Reference
DDD (๊ธฐ๋ณธ๊ฐ)
internal/
โโโ domain/{domain}/
โ โโโ alias.go # public surface (ํ์)
โ โโโ handler/ # ์ธ๋ฐ์ด๋ ์ด๋ํฐ
โ โโโ app/ # ์ ํ๋ฆฌ์ผ์ด์
์๋น์ค
โ โโโ core/model/ # ๋๋ฉ์ธ ๋ชจ๋ธ (ํ์)
โ โโโ core/repo/ # ๋ ํฌ์งํ ๋ฆฌ ์ธํฐํ์ด์ค
โ โโโ core/svc/ # ๋๋ฉ์ธ ์๋น์ค ์ธํฐํ์ด์ค
โ โโโ event/ # ๋๋ฉ์ธ ์ด๋ฒคํธ
โ โโโ infra/ # ์์๋ฐ์ด๋ ์ด๋ํฐ
โโโ app/ # ์ปดํฌ์ง์
๋ฃจํธ (DI ์์ด์ด๋ง)
โโโ server/
โ โโโ http/ # ํธ๋์คํฌํธ ๋ ์ด์ด (http, grpc, โฆ)
โโโ orchestration/ # ํฌ๋ก์ค ๋๋ฉ์ธ ์กฐ์
โโโ pkg/ # ๊ณต์ ์ ํธ๋ฆฌํฐ
alias.go ํ์, domain root์ ๋ค๋ฅธ .go ๊ธ์ง
core/model/์ ์ต์ 1๊ฐ .go ํ์
core/*, event๋ internal/pkg import ๊ธ์ง
- interface๋
core/repo/์๋ง ์ ์
internal/app/ (์ปดํฌ์ง์
๋ฃจํธ): ๋ฌด์ ํ import ๊ฐ๋ฅ
internal/server/<proto>/ (ํธ๋์คํฌํธ): internal/app/, internal/pkg/, ํ์ ํธ๋์คํฌํธ๋ง import ๊ฐ๋ฅ. ์๋ฐ ๊ท์น: dependency.transport-imports-domain, dependency.transport-imports-orchestration, dependency.transport-imports-unclassified
Clean Architecture
internal/domain/{domain}/
โโโ handler/ # ์ปจํธ๋กค๋ฌ
โโโ usecase/ # ๋น์ฆ๋์ค ๊ท์น
โโโ entity/ # ์ํฐํ๋ผ์ด์ฆ ๊ท์น
โโโ gateway/ # ๋ฐ์ดํฐ ์ ๊ทผ ์ธํฐํ์ด์ค
โโโ infra/ # ํ๋ ์์ํฌ & ๋๋ผ์ด๋ฒ
entity๋ internal/pkg import ๊ธ์ง
- DTO๋
handler/, usecase/๋ง ํ์ฉ
Layered (Spring ์คํ์ผ)
internal/domain/{domain}/
โโโ handler/ # HTTP/gRPC ํธ๋ค๋ฌ
โโโ service/ # ๋น์ฆ๋์ค ๋ก์ง
โโโ repository/ # ๋ฐ์ดํฐ ์ ๊ทผ
โโโ model/ # ๋๋ฉ์ธ ๋ชจ๋ธ
model์ internal/pkg import ๊ธ์ง
- DTO๋
handler/, service/๋ง ํ์ฉ
Hexagonal (ํฌํธ & ์ด๋ํฐ)
internal/domain/{domain}/
โโโ handler/ # ๋๋ผ์ด๋น ์ด๋ํฐ
โโโ usecase/ # ์ ํ๋ฆฌ์ผ์ด์
๋ก์ง
โโโ port/ # ์ธํฐํ์ด์ค (์ธ๋ฐ์ด๋+์์๋ฐ์ด๋)
โโโ domain/ # ์ํฐํฐ, ๊ฐ ๊ฐ์ฒด
โโโ adapter/ # ๋๋ฆฌ๋ธ ์ด๋ํฐ
domain์ internal/pkg import ๊ธ์ง
- DTO๋
handler/, usecase/๋ง ํ์ฉ
Modular Monolith
internal/domain/{domain}/
โโโ api/ # ๋ชจ๋ ๊ณต๊ฐ ์ธํฐํ์ด์ค
โโโ application/ # ์ ์ฆ์ผ์ด์ค
โโโ core/ # ์ํฐํฐ, ๊ฐ ๊ฐ์ฒด
โโโ infrastructure/ # DB, ์ธ๋ถ ์๋น์ค
core๋ internal/pkg import ๊ธ์ง
- DTO๋
api/, application/๋ง ํ์ฉ
Consumer/Worker (ํ๋ซ ๋ ์ด์์)
internal/
โโโ worker/ # ๋ฉ์์ง ํธ๋ค๋ฌ (worker_order.go โ OrderWorker.Process())
โโโ service/ # ๋น์ฆ๋์ค ๋ก์ง
โโโ store/ # ์์์ฑ
โโโ model/ # ๋ฐ์ดํฐ ๊ตฌ์กฐ์ฒด
โโโ pkg/ # ๊ณต์ ์ธํ๋ผ
model์ internal/pkg import ๊ธ์ง
worker_*.go ํ์ผ์ ๋์ํ๋ ํ์
(XxxWorker) + Process ๋ฉ์๋ ํ์
- DTO๋
worker/, service/๋ง ํ์ฉ
- ๋๋ฉ์ธ ๊ฒฉ๋ฆฌ ๊ท์น ๋ฏธ์ ์ฉ (ํ๋ซ ๋ ์ด์์)
Batch (ํ๋ซ ๋ ์ด์์)
internal/
โโโ job/ # ๋ฐฐ์น ์์
(job_expire_files.go โ ExpireFilesJob.Run())
โโโ service/ # ๋น์ฆ๋์ค ๋ก์ง
โโโ store/ # ์์์ฑ
โโโ model/ # ๋ฐ์ดํฐ ๊ตฌ์กฐ์ฒด
โโโ pkg/ # ๊ณต์ ์ธํ๋ผ
model์ internal/pkg import ๊ธ์ง
job_*.go ํ์ผ์ ๋์ํ๋ ํ์
(XxxJob) + Run ๋ฉ์๋ ํ์
- DTO๋
job/, service/๋ง ํ์ฉ
- ๋๋ฉ์ธ ๊ฒฉ๋ฆฌ ๊ท์น ๋ฏธ์ ์ฉ (ํ๋ซ ๋ ์ด์์)
Event-Driven Pipeline (ํ๋ซ ๋ ์ด์์)
internal/
โโโ command/ # ์ปค๋งจ๋ ํธ๋ค๋ฌ (command_create_order.go โ CreateOrderCommand.Execute())
โโโ aggregate/ # ์ ๊ทธ๋ฆฌ๊ฑฐํธ ๋ฃจํธ (aggregate_order.go โ OrderAggregate.Apply())
โโโ event/ # ๋๋ฉ์ธ ์ด๋ฒคํธ
โโโ projection/ # ์ฝ๊ธฐ ๋ชจ๋ธ ํ๋ก์ ํฐ
โโโ eventstore/ # ์ด๋ฒคํธ ์์์ฑ
โโโ readstore/ # ์ฝ๊ธฐ ๋ชจ๋ธ ์์์ฑ
โโโ model/ # ๊ณต์ ๊ฐ ๊ฐ์ฒด
โโโ pkg/ # ๊ณต์ ์ธํ๋ผ
event, model์ internal/pkg import ๊ธ์ง
command_*.go ํ์ผ์ ๋์ํ๋ ํ์
(XxxCommand) + Execute ๋ฉ์๋ ํ์
aggregate_*.go ํ์ผ์ ๋์ํ๋ ํ์
(XxxAggregate) + Apply ๋ฉ์๋ ํ์
- DTO๋
command/, projection/๋ง ํ์ฉ
- ๋๋ฉ์ธ ๊ฒฉ๋ฆฌ ๊ท์น ๋ฏธ์ ์ฉ (ํ๋ซ ๋ ์ด์์)
Custom
arch := core.Architecture{
Layers: core.LayerModel{
Sublayers: []string{"api", "logic", "data"},
Direction: map[string][]string{
"api": {"logic"}, "logic": {"data"}, "data": {},
},
InternalTopLevel: map[string]bool{
"module": true,
"pkg": true,
},
},
Layout: core.LayoutModel{
DomainDir: "module",
SharedDir: "pkg",
},
Naming: core.NamingPolicy{
BannedPkgNames: []string{"util", "common", "misc", "helper", "shared", "services"},
LegacyPkgNames: []string{"router", "bootstrap"},
AliasFileName: "alias.go",
},
Structure: core.StructurePolicy{
RequireAlias: false,
RequireModel: false,
},
}
์ ์ฒด ํ๋: LayerModel.Sublayers, LayerModel.Direction, LayerModel.PkgRestricted,
LayerModel.InternalTopLevel, LayerModel.LayerDirNames, LayerModel.PortLayers,
LayerModel.ContractLayers, LayoutModel.InternalRoot (default "internal",
"packages"/"src" ๋ฑ ๋นํ์ค ํจํค์ง ๋ฃจํธ ์ง์), LayoutModel.DomainDir,
LayoutModel.OrchestrationDir, LayoutModel.SharedDir, LayoutModel.AppDir,
LayoutModel.ServerDir, NamingPolicy.BannedPkgNames, NamingPolicy.LegacyPkgNames,
NamingPolicy.AliasFileName, StructurePolicy.RequireAlias,
StructurePolicy.RequireModel, StructurePolicy.ModelPath,
StructurePolicy.TypePatterns, StructurePolicy.InterfacePatternExclude
๊ฒ์ฆ์ arch.Validate() ๋๋ core.Validate(arch)๋ก ์ํํ๋ค. ๋น InternalRoot๋ cloneArchitecture ์์ ์ "internal"๋ก ์ ๊ทํ๋๋ฏ๋ก ๋ฃฐ์์๋ ํญ์ ๋น์ด์์ง ์์ ๊ฐ์ ์ฝ๋๋ค.
Domain Isolation (๋ชจ๋ ๋ชจ๋ธ ๊ณตํต)
| from โ to | domain root | domain sub-pkg | orchestration | shared pkg |
|---|
| same domain | O | O | X | O |
| other domain | X | X | X | O |
| orchestration | O | X | O | O |
| cmd | O | X | O | O |
| shared pkg | X | X | X | O |
์ฐธ๊ณ : Consumer/Worker, Batch ํ๋ฆฌ์
์ ํ๋ซ ๋ ์ด์์์ด๋ฏ๋ก ๋๋ฉ์ธ ๊ฒฉ๋ฆฌ ๊ท์น์ด ์ ์ฉ๋์ง ์์ต๋๋ค.
Options
ctx := core.NewContext(pkgs, "", "", presets.DDD(), []string{"internal/legacy/..."})
core.Run(ctx, presets.RecommendedDDD(),
core.WithSeverityOverride("dependency.cross-domain", core.Warning))
Exclude ํจํด์ ์ ๊ทํ ํ ๋งค์นญ๋จ: /internal/foo, internal/foo, internal/foo/, ./internal/foo ๋ชจ๋ ๋์ผ.
Meta Violations
๋ฐํ์์ด ํ๊ฒฝ ์ด์๋ฅผ ์๋ฆฌ๋ meta.* violation. (Rule, Message) pair๋ก dedup.
| ID | Severity | ์๋ฏธ |
|---|
meta.no-matching-packages | Warning | ๋ชจ๋ path๊ฐ ๋ก๋๋ ํจํค์ง์ ๋งค์นญ ์ ๋จ |
meta.layout-not-supported | Warning | ๋ ์ด์์ ์์กด ๋ฃฐ์ด <root>/<InternalRoot>/๋ฅผ ๋ชป ์ฐพ์ |
meta.rule-panic | Error | ๋ฃฐ panic ๋ฐ์; ๋ค๋ฅธ ๋ฃฐ์ ๊ณ์ ์คํ |
meta.unknown-violation-id | per rule | ๋ฃฐ์ด ๋ฏธ์ ์ธ violation ID emit |
Tx Boundary (opt-in)
ํธ๋์ญ์
์์ ์์น์ tx ํ์
๋์ค์ ์ฐจ๋จํฉ๋๋ค. ํ๋ก์ ํธ๋ง๋ค DB/SDK ์กฐํฉ์ด ๋ค๋ฅด๋ฏ๋ก
์์ ์ตํธ์ธ์
๋๋ค. StartSymbols, Types ๋ ๋ค ๋น์ด ์์ผ๋ฉด ํด๋น ๊ฒ์ฌ๋ ๊ฑด๋๋๋๋ค.
ruleset := presets.RecommendedDDD().With(tx.New(tx.Config{
StartSymbols: []string{"database/sql.(*DB).BeginTx"},
Types: []string{"database/sql.Tx"},
AllowedLayers: []string{"app"},
}))
์๋ฐ ๊ท์น ID: tx.start-outside-allowed-layer, tx.type-in-signature.
No Panic In Domain
presets.RecommendedDDD()์ Error ๊ท์น์ผ๋ก ํฌํจ๋๋ค. ๋๋ฉ์ธ/์ ํ๋ฆฌ์ผ์ด์
๋ ์ด์ด์์ panic, log.Fatal*, os.Exit๋ก error ๊ฒฝ๊ณ๋ฅผ ์ฐํํ๊ณ ํ๋ก์ธ์ค
์ ์ด๋ฅผ ์ง์ ๊ฐ์ ธ๊ฐ๋ ์ฝ๋๋ฅผ ์ก์ต๋๋ค.
import types "github.com/NamhaeSusan/go-arch-guard/rules/types"
ruleset := core.NewRuleSet(types.NewNoPanicInDomain(
types.WithAllowedFunctions("Must*"),
types.WithAllowedPaths("internal/domain/payment/core/model/must.go"),
))
์๋ฐ ๊ท์น ID: errors.no-panic-in-domain.
Domain Core Purity
presets.RecommendedDDD()์ Error ๊ท์น์ผ๋ก ํฌํจ๋๋ค. ๋๋ฉ์ธ ๋ด๋ถ ๋ ์ด์ด
(core/model, event, Architecture.Layers.PkgRestricted ๋ฑ)์์ time.Now,
os.Getenv, ํ์ผ create/read/write/remove helper, log.Print*, math/rand.*,
crypto/rand.Read, net/http.Get, (*http.Client).* ๊ฐ์ ๋ฐํ์ ๋ถ์ํจ๊ณผ API๋ฅผ
์ง์ ํธ์ถํ๋ ํจํด์ ์ก๋๋ค.
ํ์
-only ์ฌ์ฉ(time.Time)์ ํ์ฉํ๊ณ call expression๋ง ๊ฒ์ฌํ๋ค.
ruleset := core.NewRuleSet(dependency.NewNoSideEffectCallInCore())
ํ์ํ๋ฉด dependency.WithInspectedLayers(...), dependency.WithDeniedCalls(...),
dependency.WithAllowedCalls(...)๋ก ๊ฒ์ฌ ๋ ์ด์ด์ ํธ์ถ deny/allow ๋ชฉ๋ก์ ์กฐ์ ํ๋ค.
์๋ฐ ๊ท์น ID: purity.no-side-effect-call-in-core.
Machine-readable Output
AI ์์ด์ ํธ๋ CI ๋ด์ด violation์ ํ์ฒ๋ฆฌํด์ผ ํ๋ฉด JSON ์ถ๋ ฅ์ ์ฐ์ ํ๋ค.
data, err := report.MarshalJSONReport(violations)
if err != nil {
return err
}
fmt.Println(string(data))
Banned Patterns
| Category | Banned |
|---|
| Package names | util, common, misc, helper, shared, services |
| Legacy dirs | router, bootstrap, misplaced app/handler/infra |
| Naming | stuttering (order.OrderService), Impl suffix, non-snake_case, hand-rolled mocks |
| Placement | middleware/๋ internal/pkg/middleware/์๋ง |
DDD ์ ์ฉ: Cross-Domain Interface ๊ธ์ง
Repository ํฌํธ interface(๊ธฐ๋ณธ๊ฐ: ์ด๋ฆ์ด Repository/Repo๋ก ๋๋๋ ๊ฒ)๋ core/repo/์๋ง ์ ์ ๊ฐ๋ฅ. Consumer-defined interface(์ฌ์ฉ์ฒ์์ ์ ์ธํ๋ Go ๊ด๋ก)๋ handler/, app/, svc/ ์ด๋๋ ํ์ฉ. cross-domain ๋ฐ์ดํฐ ํ์ ์ โ orchestration/handler/๋ก ์ด๋. ๋ค๋ฅธ ์ดํ(Gateway, Adapter ๋ฑ) ์ฐ๋ ํ์ structural.WithRepoPortSuffixes(...)๋ก ๋งค์นญ suffix ์ง์ ๊ฐ๋ฅ.
| ์ฐํ ์๋ | ์ก๋ ๊ท์น |
|---|
handler/app/svc์์ *Repository/*Repo interface ์ ์ | structural.interface-placement |
handler/app/svc์์ type X = otherdomain.Repo alias | structural.interface-placement |
| alias.go์์ interface ์ง์ ์ ์ | structural.domain-alias-no-interface |
| alias.go์์ repo/svc ํ์
re-export | structural.domain-alias-contract-reexport |
Vibe-coding Smell (Warning๋ง)
๋น๋๋ฅผ ๊นจ์ง ์๊ณ ๊ฐ๋ฐ์์๊ฒ ์๋ฆฌ๋ Warning ์นดํ
๊ณ ๋ฆฌ ๋ฃฐ. ๊ฐ์ (error)๊ฐ ์๋๋ผ ๊ณ ์ง๋ค.
| ๋ฃฐ | ์ก๋ ํจํด |
|---|
errors.no-panic-in-domain | ๋๋ฉ์ธ/์ ํ๋ฆฌ์ผ์ด์
๋ ์ด์ด์ panic, log.Fatal*, os.Exit. ๋๋ฉ์ธ์ error๋ฅผ ๋ฐํํ๊ณ ๋ณต๊ตฌ/์ฌ์๋/๋กค๋ฐฑ/์ข
๋ฃ ์ ์ฑ
์ ๋ฐ๊นฅ ๋ ์ด์ด๊ฐ ์์ ํด์ผ ํ๋ค. types.NewNoPanicInDomain(). |
purity.no-side-effect-call-in-core | ๋๋ฉ์ธ ๋ด๋ถ ๋ ์ด์ด์์ time.Now, os.Getenv, ํ์ผ I/O, logging, random, net/http shortcut ๊ฐ์ ๋ฐํ์ ๋ถ์ํจ๊ณผ API๋ฅผ ์ง์ ํธ์ถํ๋ ํจํด. ๋ฐํ์ ๊ฐ์ ๋ฐ๊นฅ ๋ ์ด์ด์์ ๋ช
์์ ์ผ๋ก ์ฃผ์
ํ๋ค. dependency.NewNoSideEffectCallInCore(). |
interfaces.container-only | ํจํค์ง์์ ์ ์ธ๋ named interface๊ฐ struct field ํ์
์ผ๋ก๋ง ์ฐ์ด๊ณ ํจ์ ํ๋ผ๋ฏธํฐ/๋ฐํ์ ํ ๋ฒ๋ ์ ์ฐ์. wiring ๋ ์ด์ด๊ฐ ๊ฐ์ ๋ค๊ธฐ ์ํด ๋ง๋ ์์ ์ปจํ
์ด๋ interface ํจํด์ ์ก๋๋ค. interfaces.WithSeverity(core.Error)๋ก hard rule ์น๊ฒฉ ๊ฐ๋ฅ. |
types.no-setter | ํฌ์ธํฐ ๋ฆฌ์๋ฒ๋ฅผ ๊ฐ์ง ๋ด๋ณด๋ด๊ธฐ ์ธํฐ ๋ฉ์๋(Set*, ๋งค๊ฐ๋ณ์ 1๊ฐ ์ด์)๋ฅผ ๊ฒ์ถ. ๊ถ์ฅ ์์ : ์์กด์ฑ์ ์์ฑ์์ ๋ช
์์ ํ๋ผ๋ฏธํฐ๋ก ์ถ๊ฐ (NewService(..., dep)). With* ์ต์
์ ์ ๋ง๋ก ์ ํ์ ์ด๊ณ ์ฌ๋ฌ ์กฐํฉ์ด ํ์ํ ๊ฒฝ์ฐ์๋ง ์ฌ์ฉ โ ์ค์ ๋ฅ ์ต์
์๋ setter๋ ๋์ฒด๋ก ๋ง์ง ์์. ํ๋ฃจ์ธํธ ๋น๋(๋ฆฌ์๋ฒ ํ์
๋ฐํ ๋ฉ์๋), ํ
์คํธ ํ์ผ, testdata/ยทmocks/ ํ์ ํจํค์ง๋ ์๋ ์ ์ธ. types.NewNoSetter(). |
Cross-Domain Anonymous Interface (Hard rule, Error)
| ๋ฃฐ | ์ก๋ ํจํด |
|---|
interfaces.cross-domain-anonymous | ๋๋ฉ์ธ ์ธ๋ถ ๊ทธ๋ฆฌ๊ณ orchestration ์ธ๋ถ์์ ์ ์ธ๋ anonymous interface๊ฐ method signature์ ๋ค๋ฅธ ๋๋ฉ์ธ ํ์
์ ์ฐธ์กฐํ๋ฉด ์๋ฐ. cmd/ ๋๋ internal/pkg/ ๊ฐ์ wiring ์ฝ๋๊ฐ ๋๋ฉ์ธ ํ์
์ ๋ํด inline ad-hoc ์ถ์ํ๋ฅผ ์ ์ธํ๋ ํจํด์ ์ก๋๋ค. Severity: Error โ cross-domain ์ถ์ํ๋ orchestration ํจํค์ง๊ฐ ์์ ํ๋ค๋ ์ปจ๋ฒค์
์ ๊ฐ์ . fix: ์ด๋ํฐ๋ฅผ internal/orchestration/์ผ๋ก ์ด๋ํ๊ณ wiring ์ฝ๋๋ orchestration ์์ฑ์๋ฅผ ํธ์ถ. orchestration ํจํค์ง(์๋ธํจํค์ง ํฌํจ)๋ by-design exempt. |
Existing Project with Violations
architecture_test.go ์์ฑ ํ ์คํ โ ์ ์ฒด ์๋ฐ ํ์ธ
core.NewContext(..., exclude)๋ก ๋ง์ด๊ทธ๋ ์ด์
์ค ๊ฒฝ๋ก ์ ์ธ
- ์ ์ง์ ์ผ๋ก ์๋ฐ ์์ , exclude ์ ๊ฑฐ
- ์ ํ๊ธฐ์๋
core.WithSeverityOverride(..., core.Warning) ์ฌ์ฉ