DDD-first operating guide for coding agents in this repository.
Model behavior around the domain first. Protect domain language, business invariants, and bounded-context boundaries before optimizing frameworks, transport, or storage.
The boundaries/ directory represents bounded contexts and related interfaces:
link(Go) - core short-link lifecycle domainmetadata(Go) - enrichment and screenshot/metadata domainbff(Go) - client-facing application/API composition for webapi/api-gateway(Go) - deprecated API boundary contextproxy(TypeScript) - redirect/runtime edge serviceui(Next.js/TypeScript) - web clientmobile(Expo + Nx) - mobile clientchrome-extension(WXT + React/TS) - browser client
Read first:
boundaries/README.md- boundary-local
README.md docs/ADR/README.md
If instructions conflict, follow:
- Files in the exact service/context being edited
- Boundary-local rules:
boundaries/proxy/.cursor/rules/architecture.mdboundaries/ui/.cursor/rules/front-end-cursor-rules.mdc
- Repo-wide rules:
.cursor/.cursorrules.cursor/rules/go-microservices.mdc
- This document
- Domain layer
- Entities, Value Objects, Aggregates, Domain Services, Domain Events
- No infrastructure dependencies
- Business invariants enforced here
- Application layer
- Use cases/application services
- Orchestrates domain behavior and transactions
- DTOs for boundary contracts
- Infrastructure layer
- DB, MQ, HTTP clients, file systems, third-party SDKs
- Implements repository/gateway interfaces
- Interface/transport layer
- HTTP/gRPC handlers, CLI, messaging consumers
- Map external contracts to application DTOs
Dependency direction must always point inward (toward domain).
- Keep aggregate roots small and consistency-focused.
- Modify aggregate state only through aggregate behavior.
- Prefer immutable Value Objects.
- Repositories should load/save aggregate roots, not random tables.
- Domain events should express business facts in ubiquitous language.
- Cross-context integration should use explicit contracts (events, APIs, ACL/adapters), never hidden coupling.
- Use terms from boundary glossary/README/ADR in names.
- If domain meaning changes, update docs and naming in the same PR.
- Avoid generic names like
manager,data,utilfor domain logic.
- Identify the target bounded context.
- Identify affected aggregate/use case.
- Write change at domain/application level first; adapt infra second.
- Keep changes local to one context unless integration is required.
- Validate with the narrowest relevant tests/checks.
- Summarize domain impact (invariant, event, contract, behavior).
make helpmake depmake up/make down(heavy full stack)
There are no proto targets at the root: every .proto lives inside a boundary that owns its own
buf module. Run them from the boundary, e.g. make -C boundaries/link proto-check.
Run from context directory:
go test ./...go test -run <Name> ./...make help
Useful local targets:
boundaries/link:make dev,make docs,make e2e, plus the proto targets belowboundaries/metadata: proto targets below, docs viamake helpboundaries/bff:make dep,make generate,make docs
Proto targets, identical in boundaries/link and boundaries/metadata (require the buf CLI —
brew install bufbuild/buf/buf):
make proto-check— lint + format + breaking, the same three checks CI runsmake proto-lint,make proto-format(proto-format-fixto rewrite)make proto-breaking— compares the module against its state onmainmake proto-generate— regenerate.pb.gomake proto-push— publish the module to the BSR; manual, never done by CImake proto-dep-update— refreshbuf.lock
boundaries/proxy:pnpm install && pnpm build && pnpm lint && pnpm testboundaries/ui:pnpm install && pnpm type-check && pnpm lint && pnpm test:runboundaries/mobile:pnpm install && pnpm nx serve shortlink(orpnpm nx build shortlink)boundaries/chrome-extension:pnpm install && pnpm build && pnpm compile
- Contracts are distributed through the BSR. A boundary publishes its own module; consumers depend
on the generated SDK (
buf.build/gen/go/...ingo.mod,@buf/...inpackage.json). Never copy someone else's.protointo your boundary and never generate their contract locally. - When a
.protochanges, the order is:make proto-check→make proto-generate→ commit → manuallymake -C <boundary> proto-push→ consumers pull the new SDK viago get/pnpm update. - Regenerated code is committed in the same change as the contract it comes from.
- Regenerate derived artifacts in the same change when contracts/interfaces change.
- Do not hand-edit generated files unless unavoidable; document reason when doing so.
- Business logic inside handlers/controllers/repositories.
- Infrastructure types leaking into domain entities/value objects.
- Cross-context direct DB coupling.
- “God” services that bypass aggregate invariants.
- Renaming domain concepts without updating ubiquitous language docs.
- Check current tree before editing:
git status. - Keep commits small and domain-focused.
- Use clear English commit messages.
- Never revert unrelated user changes.
Before finalizing:
- Bounded context and aggregate boundaries were respected.
- Domain invariants are preserved and tested.
- External contracts/events are updated and validated if changed.
- Relevant tests/lint/type-check were run (or explicitly justified).
- ADR/README/glossary updates are included when domain language changed.