agents/openai.yaml
interface:
display_name: "Golang DDD Architecture"
short_description: "Design boundaries for a maintainable Go service"
default_prompt: "Use $golang-ddd-architecture to design or review this Go service architecture with clear boundaries and dependency direction."
references/architecture-rules.md
# Architecture Rules
## Layer Matrix
| Layer | Purpose | Can import | Must not import |
| --- | --- | --- | --- |
| `domain` | business rules, invariants, behavior | standard library, local domain packages | transport, protobuf, SQL, Firestore, HTTP clients, Terraform, Cloud SDKs |
| `app` | orchestration and use cases | `domain`, consumer-owned interfaces | concrete adapters, HTTP handlers, gRPC servers |
| `ports` | inbound transport | `app`, `domain` DTO mapping helpers | concrete adapter internals |
| `adapters` | outbound integrations | `app`, `domain` | inbound transport packages |
## Go-Specific Rules
- Define interfaces where they are consumed, not where they are implemented.
- Keep interfaces small enough that handwritten mocks stay trivial.
- Use `main` or a dedicated wiring package as the composition root.
- Treat import cycles as a design signal. Move the interface inward or split the use case instead of collapsing packages.
## Model Boundaries
- Separate DB models from HTTP or gRPC response models when they change for different reasons.
- Separate domain types from persistence models when storage concerns start shaping the behavior model.
- Duplicate data shapes if it removes coupling between layers.
- Prefer mapping code over one shared struct with many tags and special-case mutations.
## Package Layouts That Fit This Style
Small service:
```text
internal/trainings/
ports/http/
app/
domain/training/
adapters/
```
Service with pragmatic CQRS:
```text
internal/trainings/
ports/http/
ports/grpc/
app/command/
app/query/
domain/training/
adapters/
```
## Anti-Patterns
- HTTP or gRPC handlers directly mixing database reads, authorization, and business rules.
- One `service` type with many unrelated methods and a wide dependency interface.
- Reusing the same struct for Firestore, JSON responses, and domain behavior.
- Domain packages importing protobuf, SQL drivers, Firestore clients, or HTTP clients.
- Moving everything into one package just to avoid import cycles.
- Treating microservices as the fix for bad boundaries inside a service.
## Keep The Design Smaller When
- The service is simple CRUD over one data shape.
- The business language is not richer than create, update, delete, and list.
- Most complexity sits in infrastructure plumbing rather than domain behavior.
- A heavier split would only add ceremony without improving testability or change isolation.
## Example Project
- Practical example repo used across the book: [ThreeDotsLabs/wild-workouts-go-ddd-example](https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-example)
references/naming-and-models.md
# Naming And Models
## Business Naming
- Prefer business verbs such as `ScheduleTraining`, `CancelTraining`, `ApproveReschedule`, `MoveTraining`.
- Avoid default CRUD verbs when the business does not speak that way.
- Use the same business noun across ports, app, and domain unless a boundary demands translation.
- Rename vague technical abstractions like `Manager`, `Processor`, `Util`, or `CommonService` into the use case or concept they actually represent.
## Model Separation Heuristics
- If a field should exist in storage but not on the public API, split the models.
- If a transport schema is optimized for clients while persistence is optimized for queries, split the models.
- If the domain needs private fields or invariants, split the domain model from DB and transport models.
- If one change request repeatedly asks for "add field here, but do not expose it there", the shared model is already the wrong abstraction.
## Good Friction
Some duplication is intentional:
- mapping DB model to application or domain type,
- mapping domain or query type to HTTP or gRPC response,
- separate command payloads and query responses,
- separate internal and external update paths when security rules differ.
## Error Shaping
- Keep application errors transport-agnostic.
- Prefer typed errors or errors with stable slugs when the same application logic is exposed through HTTP and gRPC.
- Translate transport codes at the port boundary, not in the app or domain layers.
## Review Checklist
- Can a non-technical stakeholder understand the important method names?
- Can a DB field change without forcing an API change?
- Can an API response change without forcing a DB migration?
- Can domain behavior evolve without changing protobuf or JSON types first?
- Does the current naming expose business concepts rather than implementation details?
SKILL.md
---
name: golang-ddd-architecture
description: "Design and refactor Go service architecture for DDD-style systems. Use when a Go codebase needs explicit ports/app/domain/adapters boundaries, dependency rules, interface placement, composition-root dependency injection, separate transport and database models, or help deciding whether DDD, Clean Architecture, or CQRS are justified."
user-invocable: true
license: MIT
compatibility: Designed for Codex, Claude Code, Gemini CLI, Cursor, OpenCode, and similar AI coding agents working with Go services.
metadata:
author: joeyave
version: "0.3.0"
---
# Golang DDD Architecture
Use this skill to shape a Go service so it stays easy to change, test, and reason about as business logic grows.
## Start Here
- Check whether the service is complex enough to justify the pattern.
- Keep the design smaller for simple CRUD or authentication flows where read and write shapes are mostly identical and business rules are thin.
- Use the full workflow when handlers keep growing `if` trees, models are reused across boundaries, or dependencies are hard to mock or untangle.
## Workflow
1. Inventory entry points and use cases.
- Start from HTTP handlers, gRPC services, CLI commands, message consumers, and scheduled jobs.
- Rewrite the supported operations in business language before moving code around.
2. Draw the boundary map.
- `ports` are inbound transport and serialization only.
- `app` orchestrates use cases.
- `domain` owns business rules and invariants.
- `adapters` talk to databases, queues, external APIs, files, and other infrastructure.
3. Enforce dependency direction.
- `domain` depends on nothing outside itself.
- `app` may import `domain` but not concrete transport or adapter packages.
- `ports` and `adapters` may import inward layers.
- Fix import cycles by moving interfaces inward or by splitting responsibilities, not by flattening everything into one package.
4. Place interfaces next to the consumer.
- Define interfaces in the package that needs the behavior.
- Keep them narrow and use-case-oriented.
- Inject implementations from the composition root.
5. Separate models that change for different reasons.
- Do not reuse one struct for DB rows, API responses, Pub/Sub payloads, and domain state unless their change cadence is truly the same.
- Accept data duplication when it removes coupling. DRY is usually more valuable for behavior than for data.
6. Keep `main` boring.
- Use `main` as the composition root.
- Wire repositories, clients, handlers, and configuration there.
- Do not hide business logic, validation, or workflow branching there.
7. Leave the codebase more testable than you found it.
- Domain rules should be unit-testable without mocks.
- Application orchestration should be testable with tiny handwritten mocks.
- Adapter behavior should be covered with integration tests.
## Use These References
- Read [references/architecture-rules.md](references/architecture-rules.md) for layer rules, package layouts, and anti-patterns.
- Read [references/naming-and-models.md](references/naming-and-models.md) when naming, model boundaries, or shared-struct tradeoffs are the hard part.
## Deliverables
- a clear layer map or package plan,
- dependency direction that compiles without import-cycle hacks,
- constructors or wiring points in the composition root,
- explicit model boundaries,
- a short backlog of follow-up refactors if the system is too tangled for one pass.