Contents
This document helps you follow the project as a human without breaking the architecture. Deep architecture → DOMAIN.md. Writing a new module → YENI_MODUL_EKLEME.md.
Folder map in 5 minutes
cmd/server → entry; bootstrap.Run
internal/bootstrap → composition root (Build + wire_*.go)
internal/domain/X → business rules, repository PORT (interface)
internal/application/X → use case (CQRS: command / query)
internal/adapters/http → JSON API (Fiber handler)
internal/adapters/goui → panel pages (Controller)
internal/adapters/persistence/postgres → sqlc repo (port implementation)
internal/infrastructure → JWT, mail, SMS, payment, cache…
pkg/ → cross-cutting helpers (not domain)
db/queries/ → sqlc SQL source
migrations/ → schema
api/openapi.yaml → API contract
Dependency direction (memorize):
domain ← application ← adapters · infrastructure on the outside · everything wired in bootstrap.
Context-specific file lists: map/user.md, map/auth.md, map/contact.md, map/notification.md, map/audit.md, map/payment.md, map/settings.md, map/upload.md, map/rbac.md.
Application Service surface (standard)
Transport (HTTP / GoUI) reads application/<context>.Service (or existing *Service) first.
CQRS *Handler files stay internal; business rules live there.
| Context | Facade file | GoUI / HTTP field |
|---|---|---|
| User | application/user/service.go |
Users |
| Auth | application/auth/facade.go |
Auth |
| Contact | application/contact/service.go |
Contacts |
| Notification | application/notification/service.go |
Notifications |
| Audit | application/audit/service.go |
Audit |
| Settings | application/settings/service.go |
Settings |
| Payment | application/payment (ThreeDSService) |
ThreeDSSvc |
| Upload | application/upload/service.go |
Upload |
| Authz / RBAC | application/authz/service.go |
Authz |
When adding a new context: write CQRS handlers → NewService facade → transport receives Service only.
Naming glossary (single language)
| Name | Where | Purpose | What it is not |
|---|---|---|---|
Use case / *Handler (application) |
internal/application/... |
One business rule step (Login, ListUsers…) | Does not know HTTP or HTML |
| HTTP handler | internal/adapters/http/handler |
Parses JSON request → calls use case | Does not write domain logic |
| GoUI Controller | internal/adapters/goui |
Page state: Mount / Render / HandleEvent | Not a REST handler |
| Repository port | internal/domain/... |
Persistence interface | No SQL / GORM |
| Repository impl | adapters/persistence/postgres |
Fills port with sqlc | Do not put business rules here |
| Deps | handler / GoUI / use case | Constructor injection groups | Not a god-container |
| Service (facade) | application/<ctx>/service.go or facade.go |
Single surface transport reads; CQRS inside | Does not copy business rules; delegates |
| wire_*.go | internal/bootstrap |
Composition root pieces | No business logic |
Type names in the application layer may still be
*Handler(CQRS pattern). When reading, think of these as use cases; do not confuse with HTTP handlers.
How does a request flow? (general)
Request
→ Fiber middleware (auth, i18n, RBAC, idempotency…)
→ HTTP handler OR GoUI Controller
→ application.<Context>Service (facade)
→ CQRS use case (command/query handler)
→ domain port (Repository, …)
→ postgres/sqlc adapter
→ response: JSON | HTML/WS diff
API and panel call the same Service / use case; business rules stay in one place.
Golden path A — User list (API)
GET /api/v1/users (permission: users:list)
Reading order:
- Route —
internal/adapters/http/server.go(protected.Get("/", … List)) - Transport —
internal/adapters/http/handler/user_handler.go→List - Use case —
internal/application/user/service.go(Servicefacade) →query.go(ListHandler) - Access —
internal/application/user/access.go - Port —
internal/domain/user(Repository) - Impl —
internal/adapters/persistence/postgres/user_repository.go - SQL —
db/queries/(user-related queries) - Wiring —
internal/bootstrap/wire_user.go+wire_http.go
Detailed file map: map/user.md.
Pagination: The panel uses page + limit (offset). The REST API on the same endpoint supports optional keyset pagination via the cursor query parameter; when next_cursor is present in the response, pass it for the next page. Offset fields are kept for backward compatibility.
Golden path B — Same list (panel)
GET /dashboard/users → screen users
Reading order:
- Route —
internal/adapters/goui/routes.go(pageRoutes, path/dashboard/users) - Factory —
internal/adapters/goui/controllers.go→controllerFor - Page —
internal/adapters/goui/controller_account_users.go(usersListController) - Use case — same
application/userService→List(same as API) - Wiring —
internal/bootstrap/wire_goui.go(UserDeps)
Golden path C — Login (API + panel)
| Surface | Entry point |
|---|---|
| API | POST /api/v1/auth/login → handler/auth_handler.go → application/auth Service.Login → LoginHandler |
| Panel | /auth/login → controller_public_auth.go → same Service.Login (web OAuth via authServiceWeb) |
File map: map/auth.md. Deep dive: AUTH.md.
Single-process memory (no Redis)
Redis is not required for single-instance deployment. The following components use in-process memory:
| Component | Location | Notes |
|---|---|---|
| JWT refresh token store | security.MemoryTokenStore |
Session rotation |
| Login guard / IP limiter | security.MemoryLoginGuard, MemoryIPRateLimiter |
Brute-force |
Authz Resolver |
application/authz/resolver.go |
Role-permission TTL cache |
Settings Service |
application/settings/service.go |
Platform settings cache |
Horizontal scaling (multiple processes) requires shared store + invalidation — currently out of scope. Details: DEPLOY.md.
Smoke / golden-path tests
Lightweight checks (full DB bootstrap not required):
| Test | Package | What it verifies |
|---|---|---|
| Cursor encode/decode | pkg/pagination/cursor_test.go |
Keyset cursor roundtrip |
| Keyset SQL generation | adapters/persistence/postgres/user_keyset_test.go |
sqlc keyset queries compile |
| User list surface | application/user/smoke_test.go |
ListQuery.Cursor, Service.List |
For full integration use fake ports via bootstrap/testkit; production wiring stays in wire_*.go files.
go test ./pkg/pagination/... ./internal/application/user/... ./internal/adapters/persistence/postgres/... -count=1
"Where do I look?" quick table
| Question | First file |
|---|---|
| Which code handles this URL? (API) | adapters/http/server.go |
| Which screen is this URL? (panel) | adapters/goui/routes.go |
| Where is the business rule? | application/<context>/service.go (facade) → CQRS files |
| Entity / invariant? | domain/<context>/ |
| SQL? | db/queries/ + persistence/postgres/ |
| How are dependencies wired? | bootstrap/wire_*.go |
| Permission constant? | pkg/rbac |
| How to add a new context? | YENI_MODUL_EKLEME.md |
Writing checklist (short)
New feature or fix:
- Find the context (
user,auth,payment…) — if missing, see YENI_MODUL_EKLEME.md - Domain entity/port if needed
- Application use case (command or query)
- Persistence (sqlc + repo) if needed
- HTTP and/or GoUI transport
- RBAC permission if needed:
pkg/rbac+ route - Relevant
wire_*.goinbootstrap - Update OpenAPI / i18n if needed
go testrelevant packages
Full bootstrap not required in tests — see bootstrap/testkit.