Ерөнхий бүтэц¶
Платформ нь Clean Architecture зарчмаар бүтээгдсэн: handler → usecase →
repository → domain. Business core нь web framework-ийг import хийдэггүй, domain
нь дотоод юуг ч import хийдэггүй.
Хоёр репо, нэг платформ¶
graph LR
subgraph app["gerege-app-mn (энэ репо)"]
M["backend/cmd/api/main.go<br/>~30 мөр"]
F["frontend/ — Next.js BFF"]
end
subgraph core["platform-core (дундын модуль)"]
S["cmd/api/server — composition root"]
U["core/business/usecases — 25 модуль"]
R["core/datasources — pgx + redis"]
G["migrations/ — embed хийсэн SQL"]
end
M --> S
S --> U --> R
S --> G
F -->|"server→server"| S
Backend нь суурийн лавлах хэрэгжилт (reference deployment): өөрийн маршрут
байхгүй, бүх чадвар модулиас ирнэ. Аппын өөрийн маршрут нэмэх цэг нь
main.go-д тэмдэглэгдсэн:
server.ServiceName = "gerege-app"
app, err := server.NewApp()
// app.Router().Route("/api/xxx", xxx.Routes(app.Pool()))
app.Run()
Яагаад ийм нимгэн вэ?
Аюулгүй байдлын нөхөөс, RLS policy, audit chain, OIDC зэрэг нь нэг
модульд амьдарна. platform-core-ийн шинэ хувилбар гарахад Dependabot
түүнийг татаж, CI нь бүх шалгуурыг дахин ажиллуулна.
Хүсэлтийн зам¶
Интернэт
│
▼ edge nginx (TLS, HSTS, rate limit)
├─ /oauth2/*, /.well-known/*, /userinfo ─► Go API — OIDC issuer
├─ /rp/sign/* ─► eID sign relay
├─ /admin/api/v1/* ─► OAuth client admin API (loopback)
└─ бусад бүх зам ─► Next.js BFF (:3000)
│ BACKEND_URL
▼
Go API (:8080)
│
дотоод сүлжээ: db · redis
Браузер хэзээ ч Go API руу шууд хандахгүй — зөвхөн ижил-origin Next.js route-ууд руу. Дэлгэрэнгүйг Frontend (BFF)-ээс үз.
Давхаргууд¶
┌──────────────────────────────────────────────────────────────┐
│ HTTP давхарга │
│ cmd/api/server → middleware stack → core/http/handlers/v1 │
│ core/http/{routes, datatransfers, middlewares, auth} │
│ + core/provider/{adminapi, adminkeys, devapps, signrelay} │
├──────────────────────────────────────────────────────────────┤
│ Usecase давхарга — core/business/usecases/* (25 контекст) │
├──────────────────────────────────────────────────────────────┤
│ Repository давхарга — core/datasources/repositories/ │
│ {interface, postgres} — гар бичмэл SQL, RLS транзакц │
├──────────────────────────────────────────────────────────────┤
│ Domain давхарга — core/business/domain (import-гүй) │
└──────────────────────────────────────────────────────────────┘
Дүрэм: usecase нь зөвхөн repositories/interface (_interface package)-ээс
хамаарна, postgres adapter-ыг хэзээ ч import хийхгүй. Ингэснээр usecase-ийн
тестүүд mock дээр ажиллаж, DB шаардахгүй.
Middleware stack¶
server.go дотор global middleware-ууд энэ дарааллаар суулгагдана — дараалал
чухал:
| # | Middleware | Юу хийдэг |
|---|---|---|
| 1 | Tracing | Хүсэлт бүрд OTel span нээнэ (request-ID лог-оос өмнө trace_id тогтоно) |
| 2 | Request ID | X-Request-ID үүсгэж context + logger руу дамжуулна |
| 3 | Recoverer | Panic-ийг барьж цэвэр 500 буцаана (request_id-тэй) |
| 4 | Metrics | Prometheus тоолуур + latency |
| 5 | Security headers | HSTS, CSP, nosniff, frame options, COOP/COEP/CORP |
| 6 | CORS | ALLOWED_ORIGINS allow-list (wildcard зөвхөн dev) |
| 7 | Body size limit | Global дээд хязгаар (route тус бүр чангаруулна) |
| 8 | Access log | Бүтэцлэгдсэн нэг мөрийн лог |
| 9 | Timeout | Ерөнхийдөө 30с; /api/v1/ai/* дээр 50с |
Route тус бүрийн middleware:
- Auth — JWT bearer шалгаж
CurrentUser-ийг context-д хийж, RLS identity тогтооно (rls.WithAdmin/rls.WithUser). - Service RLS context — нэргүй
/authбүлэгт итгэмжитserviceRLS role тавьж, нэвтрэхээс өмнөх урсгалыг (eID upsert, refresh identity хайлт) ажиллуулна. - RBAC —
RequirePermission/RequireAdmin/RequireSuperAdmin. Resolver алдаа гарвал fail-closed. - Observability gate —
/metricsба/swagger/doc.json-ыг хаана. - Rate limiter — 4 тусдаа:
| Limiter | Хязгаар | Хаана |
|---|---|---|
| auth | ~5/мин | /v1/auth/* (4 KiB body cap) |
| ai | ~20/мин (burst 10) | /v1/ai/* — орчуулгын stream ~8 chunk/мин |
| eID poll | ~60/мин (burst 30) | long-poll |
| gov бичих | ~30/мин (burst 15) | gov / assets / gspace / eID profile |
clientIP() нь X-Forwarded-For-д зөвхөн TRUSTED_PROXIES-аас итгэнэ —
өгөгдмөлөөр итгэхгүй (fail-safe).
Хариултын формат¶
Handler-ийн гарын үсэг func(w, r) error, v1.Wrap-ээр боогдоно:
func (h *handler) Something(w http.ResponseWriter, r *http.Request) error {
var req requests.Something
if err := v1.DecodeBody(r, &req); err != nil { return err }
if err := validators.ValidatePayloads(req); err != nil { return err }
out, err := h.usecase.Do(r.Context(), req.ToDomain())
if err != nil { return err } // apperror → HTTP статус
return v1.NewSuccessResponse(w, out)
}
Usecase-ууд apperror.* буцаана; handler_base_response.go түүнийг HTTP
статус руу буулгана. Дотоод шалтгааныг apperror.InternalCause-аар боож,
library-ийн алдаа клиентэд хэзээ ч хүрэхгүй.
Ops endpoint-үүд¶
| Зам | Юу | Production дээр |
|---|---|---|
/health |
Liveness | нээлттэй |
/metrics |
Prometheus | OBSERVABILITY_TOKEN bearer-ээр хаагдана |
/swagger/* |
Swagger UI + JSON | мөн адил хаагдана |
Migration¶
Numbered SQL файлууд platform-core/migrations/-д (N_name.up.sql +
.down.sql), Go-д embed хийгдсэн. Compose-ийн migrate үйлчилгээ up бүрд
ажиллана. Одоогоор 88 файл (44 migration) — RBAC, RLS, gateway, registry,
relay, OIDC provider, pgvector мэдлэгийн сан хүртэл.
Дугаарлалтын муж
Аппын өөрийн migration нэмэхдээ platform-core-ийн мужаас гадуур
дугаарлана (migrations/RANGE файлыг үз) — эс бөгөөс дугаар мөргөлдөнө.
Цааш нь¶
- Frontend (BFF) — браузерын тал, токен тусгаарлалт
- Өгөгдөл ба RLS — мөрийн түвшний хамгаалалт
- API лавлах — бүх endpoint