Агуулгыг алгасах

Ерөнхий бүтэц

Платформ нь 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 бүлэгт итгэмжит service RLS role тавьж, нэвтрэхээс өмнөх урсгалыг (eID upsert, refresh identity хайлт) ажиллуулна.
  • RBACRequirePermission / 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 файлыг үз) — эс бөгөөс дугаар мөргөлдөнө.

Цааш нь