پرش به محتویات

معماری بک‌اند

معماری پایه یک Modular Monolith است: یک Repository و یک Go Module با Binaryهای جدا برای API و Worker، ماژول‌های مستقل بر اساس Feature و یک PostgreSQL مشترک. این انتخاب، تراکنش‌های مالی و چندماژولی را ساده نگه می‌دارد و هزینه عملیاتی Microservice زودهنگام را تحمیل نمی‌کند.

اصول الزام‌آور

  • هر Feature مالک مدل دامنه، Use Case، Port، Adapter، Query و Test خود است.
  • وابستگی همیشه از Adapter به Application و از Application به Domain است؛ Domain به HTTP، SQL، Framework یا Provider وابسته نمی‌شود.
  • Interface در سمت مصرف‌کننده تعریف می‌شود و فقط وقتی بیش از یک پیاده‌سازی، Fake آزمون یا مرز واقعی وجود دارد ساخته می‌شود.
  • ماژول‌ها جدول‌های یکدیگر را مستقیم Query یا Update نمی‌کنند؛ تعامل از Application Port یا رویداد داخلی نسخه‌دار انجام می‌شود.
  • چرخه وابستگی، Package عمومی مبهم و پوشه‌های فراگیری مانند utils، helpers، models و services ممنوع‌اند.
  • Shared Kernel فقط شامل مفهوم‌های واقعا مشترک و پایدار مانند شناسه، زمان، پول و خطای پایه است.
  • هیچ Feature نباید برای استفاده مجدد، منطق دامنه خود را به platform منتقل کند.
  • مرز HTTP فقط از net/http و http.ServeMux استفاده می‌کند؛ Router یا Web Framework بیرونی بدون ADR تاییدشده مجاز نیست.
  • هر Behavior، Policy، Validation، Mapping و Middleware یک مالک روشن دارد و تکرار آن در Feature یا Layer دیگر ممنوع است.
  • DRY فقط برای رفتار واقعا یکسان و پایدار اعمال می‌شود و نباید جهت Dependency یا مرز Clean Architecture را بشکند.

نمای معماری

flowchart LR Client[وب و موبایل] --> API[HTTP Adapter] API --> App[Application] Worker[Worker Adapter] --> App App --> Domain[Domain] App --> Ports[Output Ports] PGAdapter[PostgreSQL Adapter] -. پیاده‌سازی .-> Ports ObjectAdapter[Storage Adapter] -. پیاده‌سازی .-> Ports ProviderAdapter[Provider Adapters] -. پیاده‌سازی .-> Ports PGAdapter --> PG[(PostgreSQL)] ObjectAdapter --> Object[Object Storage] ProviderAdapter --> Providers[SMS و Push و Providerها]

در این نمودار، Adapterهای HTTP و Worker ورودی‌های سیستم‌اند. Application جریان کار را هماهنگ می‌کند، Domain قواعد کسب‌وکار را نگه می‌دارد و Adapterهای خروجی Portهای موردنیاز Application را برای PostgreSQL و سرویس‌های بیرونی پیاده‌سازی می‌کنند.

چیدمان Repository

backend/
├── cmd/
│   ├── api/main.go
│   ├── worker/main.go
│   └── migrate/main.go
├── internal/
│   ├── identity/
│   │   ├── domain/
│   │   ├── application/
│   │   ├── ports/
│   │   └── adapters/
│   │       ├── http/
│   │       └── postgres/
│   ├── buildings/
│   ├── tickets/
│   ├── finance/
│   ├── notifications/
│   └── platform/
│       ├── config/
│       ├── database/
│       ├── observability/
│       └── httpserver/
├── db/migrations/
├── api/openapi/
├── deployments/
├── tests/
│   ├── integration/
│   └── contract/
├── go.mod
├── Dockerfile
└── compose.yaml

نام Featureها در internal باید جمع یا مفرد بودن ثابتی داشته باشد؛ در این پروژه نام پوشه Feature جمع انتخاب می‌شود. Packageهای داخلی کوتاه و مفرد باقی می‌مانند.

مسئولیت لایه‌ها

لایه مسئولیت موارد ممنوع
Domain Entity، Value Object، Invariant، Domain Error و رفتار خالص SQL، JSON، HTTP، Logger و SDK Provider
Application Use Case، Transaction Boundary، Authorization و هماهنگی Portها جزئیات Transport و Query خام
Ports قراردادهای ورودی و خروجی موردنیاز Use Case Interfaceهای حدسی و بسیار بزرگ
Adapters HTTP، PostgreSQL، SMS، Push و Object Storage تصمیم کسب‌وکار مستقل از Use Case
Platform Bootstrap، Config، Telemetry و زیرساخت مشترک مدل یا قاعده مختص Feature

Entity دامنه با DTO ورودی HTTP و Row پایگاه داده یکی نیست. Mapping صریح در مرز Adapter انجام می‌شود تا تغییر قرارداد بیرونی، مدل دامنه را آلوده نکند.

Composition Root و Binaryها

  • cmd/api و cmd/worker فقط Config را می‌خوانند، Dependencyها را می‌سازند، Lifecycle را مدیریت می‌کنند و اجرا را آغاز می‌کنند.
  • API و Worker از Application مشترک استفاده می‌کنند، ولی Process و Scaling مستقل دارند.
  • Migration یک فرایند صریح و یک‌بارمصرف است و با شروع هر Replica به‌طور خودکار اجرا نمی‌شود.
  • Shutdown باید دریافت کار جدید را متوقف، درخواست‌ها و Jobهای در حال اجرا را تا Deadline کامل و سپس Connectionها را ببندد.
  • تمام Serverها ReadHeaderTimeout، ReadTimeout، WriteTimeout، IdleTimeout و محدودیت اندازه Body دارند.

مرز Feature و تراکنش

Use Case مالک تراکنش است. Repository نباید مستقل و پنهانی تراکنش جدید باز کند. برای چند Repository از یک UnitOfWork کوچک یا pgx.Tx محصور در Adapter استفاده می‌شود و Domain از نوع‌های pgx بی‌خبر می‌ماند.

تراکنش چند Feature فقط برای Invariant هم‌زمان و ضروری مجاز است. اثر بیرونی مانند SMS، Push یا Webhook داخل تراکنش شبکه‌ای اجرا نمی‌شود؛ رویداد Outbox در همان تراکنش ثبت و بعدا پردازش می‌شود.

معیار استخراج سرویس

Feature فقط زمانی از Monolith جدا می‌شود که حداقل یکی از شرایط زیر با Metric اثبات شده باشد:

  • الگوی Scaling آن به‌صورت پایدار با بقیه سیستم متفاوت باشد.
  • مرز امنیتی یا الزامات بهره‌برداری مستقل داشته باشد.
  • Release مستقل، مالک تیمی مستقل یا Failure Isolation واقعی لازم باشد.
  • محدودیت فناوری با اجرای درون Monolith قابل حل نباشد.

قبل از استخراج، قرارداد API/Event، مالک داده، مسیر Migration، سازگاری عقب‌رو و Rollback باید در RFC و ADR ثبت شود.

وابستگی به اسناد محصول

نام و مرز Feature باید با مدل‌های دامنه و ماتریس نقش و ماژول سازگار باشد. Backend نباید با ساخت Entity یا Workflow جدید، تصمیم محصول را پنهانی تغییر دهد.

معیار پذیرش

  • go list -deps چرخه یا واردکردن Adapter یک Feature توسط Domain Feature دیگر نشان ندهد.
  • هر Feature Test مستقل Application و Integration داشته باشد.
  • API و Worker از یک Composition Root قابل بررسی ساخته شوند.
  • هیچ فراخوانی Provider در تراکنش پایگاه داده وجود نداشته باشد.
  • هر انحراف معماری ADR تاییدشده داشته باشد.

منابع رسمی