302 lines
14 KiB
Markdown
302 lines
14 KiB
Markdown
# Darano Monorepo — DDD / Clean Architecture Refactoring Plan
|
|
|
|
## Executive Summary
|
|
|
|
The five codebases (`api/`, `auth/`, `wallet/`, `AdminPanel/`, and `proto/`) share the same general idea of layering but diverge in naming, directory structure, config libraries, where business logic lives, and — critically for AdminPanel — **how they access data**. AdminPanel bypasses all Go backend services and writes directly to the core database, making it a parallel implementation of business logic. This plan defines a unified target architecture and a phased migration that preserves functionality at every step.
|
|
|
|
---
|
|
|
|
## 1. Current State Analysis
|
|
|
|
### 1.1 Naming & Structure Inconsistencies
|
|
|
|
| Concern | `api/` | `auth/` | `wallet/` |
|
|
|---|---|---|---|
|
|
| Business logic layer | `handler/` (HTTP-only) | `usecase/` | `core/walletImp/` |
|
|
| Business logic layer (2nd) | `service/` (gRPC client) | (same as above) | `core/marketImp/`, `core/alertImp/` |
|
|
| Data access | No repo (gateway only) | `repository/` | `repository/` |
|
|
| Domain models | `domain/stub/` only | `domain/db/` + `domain/dto/` | `domain/db/` |
|
|
| Config library | `knadh/koanf` | `kkyr/fig` | `kkyr/fig` |
|
|
| Config global | `config.Cfg` | `config.Cfg` | `config.Cfg` |
|
|
| gRPC server impl | N/A (HTTP-only) | `usecase/*` implements gRPC server | `core/*Imp/*` implements gRPC server |
|
|
|
|
**Key observation**: `auth/` has the cleanest pattern (`usecase/` for business logic), `wallet/` is the most messy (`core/` mixes gRPC server with business logic), and `api/` is fine as an HTTP gateway.
|
|
|
|
### 1.2 AdminPanel Architecture — Direct DB Access, No Abstraction
|
|
|
|
AdminPanel has a fundamentally different problem: it **bypasses all Go backend services** and connects directly to the core database.
|
|
|
|
```
|
|
AdminPanel ──direct Postgres──→ core_db (same DB as Go services)
|
|
──→ default_db (Django's own tables)
|
|
──→ lite_db (SQLite, fallback)
|
|
```
|
|
|
|
**Key problems**:
|
|
|
|
1. **`managed = False` GORM models copied manually**: Every model in `src/coreLogic/models.py` has `managed = False` and `db_table = "..."` — they are **manual replicas** of the Go services' GORM models. Generated via `inspectdb`, never kept in sync. `make proto` generates betterproto stubs, but Django models are **not auto-generated** from those.
|
|
|
|
2. **No service layer**: The entire AdminPanel is a thin layer of Django admin widgets on top of raw SQL tables. Business logic is scattered across:
|
|
- `src/coreLogic/admin/asset.py` — inline validation (`check_token_policy`, `auto_gen`) duplicating Go-side rules
|
|
- `src/coreLogic/acl.py` — permission rules
|
|
- `src/usermapper/user_perm.py` — asset access control
|
|
- Inline admin methods (`save_model`) that bypass Go services' validation entirely
|
|
|
|
3. **No gRPC integration**: The AdminPanel reads/writes directly to `core_db`. No gRPC call to Go services. Means:
|
|
- Admin edits to `Assets` bypass trustline updates
|
|
- Admin edits to `Transactions` don't trigger blockchain operations
|
|
- Admin edits to `Wallets` don't update Stellar balances
|
|
- Race conditions between admin edits and Go service operations
|
|
|
|
4. **Hardcoded business rules in Django**: `check_token_policy` duplicates validation that should live in the wallet service. `auto_gen` generates metadata that should be produced by the wallet service.
|
|
|
|
5. **Multi-database with fragile router**: `src/adminpanel/db/routers.py` routes `coreLogic` models to `core_db`. If a new model is added to Go services but forgotten in Django admin, it silently fails.
|
|
|
|
6. **Inline business logic in Django admin**: `MultiDBModelAdmin.save_model` has asset-level access control. `delete_model` does soft deletes. Mixed with admin layer, not separated.
|
|
|
|
### 1.3 Config Library Mismatch
|
|
|
|
- `api/` uses `knadh/koanf/v2` with TOML parser. Tags are `koanf:"field-name"`.
|
|
- `auth/` and `wallet/` use `kkyr/fig`. Tags are `fig:"field-name"`.
|
|
- All three use a **global singleton** `config.Cfg`.
|
|
- `api/` hardcodes defaults in `ParseConfig`; `auth/` and `wallet/` rely on struct defaults.
|
|
- AdminPanel uses `django-environ` (`.env` files). Different language/framework entirely.
|
|
|
|
### 1.4 Where Business Logic Lives
|
|
|
|
- **auth/ `usecase/`** — Decent separation, but gRPC interfaces embedded directly in the struct. Validation, business rules, and gRPC response building mixed in the same methods.
|
|
- **wallet/ `core/walletImp/`** — Problematic: 19 files implementing `WalletServiceServer` with business logic, validation, DB calls, and Stellar blockchain calls all in one method. Example: `UserInitWallet` does validation, DB transactions, key generation, trustline creation, and status response — all in one method.
|
|
- **api/ `handler/`** — Acceptable: handlers call `DaranoService` (gRPC client). No business logic here. But `service/` is a gRPC client aggregator with reflection-based URL lookup, not a traditional service layer.
|
|
- **AdminPanel `admin/`** — Wrong layer: business validation, metadata generation, and policy checks live in Django admin classes, not in a service layer.
|
|
|
|
### 1.5 Domain Model vs Persistence Model Confusion
|
|
|
|
All Go services use GORM models (`domain/db/`) directly as domain objects. No separation between **domain entities** (rich business objects with behavior) and **persistence models** (plain structs with GORM tags).
|
|
|
|
AdminPanel uses Django models with `managed = False` — the same conflation, but in Python.
|
|
|
|
---
|
|
|
|
## 2. Target Architecture
|
|
|
|
### 2.1 Go Services — Unified Directory Structure
|
|
|
|
Each Go service should follow:
|
|
|
|
```
|
|
service/
|
|
├── cmd/ # CLI entry points (Cobra)
|
|
├── domain/ # Innermost layer — pure business logic
|
|
│ ├── entity/ # Domain entities (rich, no persistence tags)
|
|
│ ├── valueobject/ # Immutable value objects (Money, AssetID, etc.)
|
|
│ ├── service/ # Domain services (orchestrate entities)
|
|
│ ├── repository/ # Repository INTERFACES only (ports)
|
|
│ ├── error/ # Domain-specific errors
|
|
│ ├── event/ # Domain events
|
|
│ ├── dto/ # Data transfer objects (gRPC/HTTP)
|
|
│ └── stub/ # Generated protobuf code (never edit)
|
|
├── application/ # Application layer — use cases
|
|
│ ├── usecase/ # Business logic / use cases
|
|
│ └── service/ # Application services
|
|
├── infrastructure/ # Infrastructure layer
|
|
│ ├── repository/ # Repository implementations (DB, Redis, Queue)
|
|
│ │ ├── db/ # PostgreSQL, etc.
|
|
│ │ ├── redis/ # Redis
|
|
│ │ └── queue/ # RabbitMQ
|
|
│ ├── config/ # Configuration loading
|
|
│ ├── grpc/ # gRPC server setup & registration
|
|
│ ├── logger/ # Logger initialization
|
|
│ └── crypto/ # Cryptography utilities
|
|
├── interface/ # Interface/Adapter layer
|
|
│ ├── http/ # HTTP handlers (api gateway only)
|
|
│ ├── grpc/ # gRPC server implementations
|
|
│ └── ws/ # WebSocket handlers
|
|
├── util/ # Cross-cutting utilities
|
|
├── go.mod
|
|
└── main.go
|
|
```
|
|
|
|
**Dependency flow** (points inward):
|
|
```
|
|
interface/ → application/ → domain/ ← infrastructure/
|
|
```
|
|
|
|
### 2.2 AdminPanel — Thin Admin Wrapper Around Go Services
|
|
|
|
AdminPanel needs a Django-native approach (no forced DDD naming), but the architectural principles are the same:
|
|
|
|
```
|
|
AdminPanel/src/
|
|
├── adminpanel/ # Django project config
|
|
├── coreLogic/
|
|
│ ├── domain/ # NEW: extracted business logic
|
|
│ │ ├── services/ # Validation, metadata generation (moved from admin/)
|
|
│ │ └── repositories/# Interfaces to backend
|
|
│ ├── application/ # NEW: use cases that delegate to gRPC
|
|
│ │ ├── asset_uc.py
|
|
│ │ ├── wallet_uc.py
|
|
│ │ └── market_uc.py
|
|
│ ├── infrastructure/ # NEW: gRPC client to Go services
|
|
│ │ └── grpc_client.py
|
|
│ ├── admin/ # Keep — now thin UI adapters only
|
|
│ ├── models.py # Keep as legacy (read-only ORM, managed=False)
|
|
│ └── enums.py # Keep
|
|
├── usermapper/ # Admin user management (keep as-is)
|
|
├── alerts/ # Notification system (keep as-is)
|
|
├── accounts/ # SMS accounts (keep as-is)
|
|
└── utils/ # Cross-cutting utilities
|
|
```
|
|
|
|
**Key principle**: AdminPanel should be a **thin admin wrapper** around the Go services, not a parallel implementation. Go services own all business logic. AdminPanel provides a human-friendly interface to view and (with validation) modify state.
|
|
|
|
### 2.3 AdminPanel Data Access Pattern
|
|
|
|
```
|
|
Read path: AdminPanel ──SQL──→ core_db (fast, direct, for list/detail views)
|
|
Write path: AdminPanel ──gRPC──→ Go services (validate + execute business logic)
|
|
```
|
|
|
|
### 2.4 Config Standardization
|
|
|
|
All Go services → `knadh/koanf/v2` with TOML. No global singletons — config passed via constructor injection.
|
|
|
|
---
|
|
|
|
## 3. Migration Plan — Phased Approach
|
|
|
|
### Phase 0: Preparation (1 day)
|
|
|
|
No code changes. Document test coverage, verify all services build, create tracking doc.
|
|
|
|
### Phase 1: Standardize Go Config (2-3 days)
|
|
|
|
Replace `fig` → `koanf` in `auth/` and `wallet/`. Move config to `infrastructure/config/config.go`. Remove global `config.Cfg`.
|
|
|
|
**Risk**: Low. Config struct fields stay the same; only the loader changes.
|
|
|
|
### Phase 2: Reorganize `auth/` (3-4 days)
|
|
|
|
Rename layers, move interfaces to `domain/`, move GORM models to `infrastructure/`. `auth/` already has the cleanest pattern — this validates the approach.
|
|
|
|
**Risk**: Low. Structure change, minimal logic changes.
|
|
|
|
### Phase 3: Reorganize `wallet/` (1-2 weeks)
|
|
|
|
Rename `core/` → `application/`. Split `walletImp/`, `marketImp/`, `alertImp/` into `application/usecase/`. Move GORM models and interfaces. Split gRPC server registration from use cases. Move `port/stellar/` → `infrastructure/`.
|
|
|
|
**Risk**: Medium. Incremental: rename first, then split gRPC, then add entities.
|
|
|
|
### Phase 4: Restructure `api/` (3-4 days)
|
|
|
|
Rename `handler/` → `interface/http/`, `service/` → `infrastructure/grpcclient/`, move `middlewares/` → `interface/http/middleware/`.
|
|
|
|
**Risk**: Medium. Gateway pattern is different from auth/wallet.
|
|
|
|
### Phase 5: AdminPanel Service Layer (2-3 weeks)
|
|
|
|
1. Create `infrastructure/grpc_client.py` — reusable gRPC client to Go services
|
|
2. Extract business logic from admin classes to `coreLogic/domain/services/`
|
|
3. Create use case layer `coreLogic/application/`
|
|
4. Refactor admin classes to thin adapters (delegate to use cases)
|
|
5. Fix router typo (`no_migartion` → `no_migration`)
|
|
6. Add gRPC write hooks for `save_model`
|
|
|
|
**Risk**: Medium. Test every admin page after migration.
|
|
|
|
### Phase 6: Shared Domain Types (3-5 days)
|
|
|
|
Extract `WalletID`, `UserID`, `AssetID`, `Balance` value objects into monorepo root `shared/domain/`. Import via each service's `go.mod`.
|
|
|
|
**Risk**: Medium. Affects all services on change.
|
|
|
|
### Phase 7: Domain Events (2-3 days, optional)
|
|
|
|
Add `domain/event/` to Go services for cross-domain communication.
|
|
|
|
---
|
|
|
|
## 4. Migration Order
|
|
|
|
```
|
|
Phase 0: Preparation
|
|
↓
|
|
Phase 1: Go config standardization
|
|
↓
|
|
Phase 2: Reorganize auth/ ──┐
|
|
├──→ Phase 5: AdminPanel (parallel with 2-4)
|
|
Phase 3: Reorganize wallet/ ─┘
|
|
↓
|
|
Phase 4: Restructure api/
|
|
↓
|
|
Phase 6: Shared domain types
|
|
↓
|
|
Phase 7: Domain events (optional)
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Before/After: AdminPanel Asset Management
|
|
|
|
**Before** — business logic in Django admin:
|
|
```python
|
|
# src/coreLogic/admin/asset.py
|
|
@admin.register(Assets, site=admin_site)
|
|
class AssetAdmin(MultiDBModelAdmin):
|
|
def save_model(self, request, obj, form, change):
|
|
if user_perm.can_access_asset(request.user, obj):
|
|
return super().save_model(request, obj, form, change)
|
|
# Direct DB write — bypasses wallet service!
|
|
|
|
def check_token_policy(self, request, obj: Assets) -> bool:
|
|
# Validation duplicated from wallet service
|
|
if obj.can_buy:
|
|
p = AssetPrices.objects.filter(asset=obj).last()
|
|
if not p or p.ico_price <= 0:
|
|
errors.append("ico price must be > 0")
|
|
```
|
|
|
|
**After** — thin admin delegating to Go service:
|
|
```python
|
|
# src/coreLogic/application/asset_uc.py
|
|
class AssetUseCase:
|
|
def __init__(self, grpc_client: GRPCClient):
|
|
self.grpc_client = grpc_client
|
|
|
|
def update_asset(self, admin_user, asset_id, changes: dict) -> StatusRes:
|
|
"""Delegates all mutations to wallet service."""
|
|
return self.grpc_client.wallet_service.InternalWalletUpdateAsset(
|
|
AssetUpdateReq(id=asset_id, changes=changes)
|
|
)
|
|
|
|
# src/coreLogic/admin/asset.py
|
|
@admin.register(Assets, site=admin_site)
|
|
class AssetAdmin(MultiDBModelAdmin):
|
|
list_display = [...]
|
|
# ... UI config only
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
uc = AssetUseCase(self.grpc_client)
|
|
uc.update_asset(request.user, obj.id, form.cleaned_data)
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Risk Mitigation
|
|
|
|
| Risk | Mitigation |
|
|
|---|---|
|
|
| Breaking CI/CD | Keep `make build` and `make run` working at every phase |
|
|
| Lost functionality during rename | Use `git mv` + `sed` for mass renames; commit each file move separately |
|
|
| gRPC contract breaks | Proto definitions are in `proto/`. We only change Go implementation |
|
|
| Config migration issues | Keep old config format identical; only change the loader |
|
|
| AdminPanel write conflicts | gRPC write path ensures Go services own all mutations |
|
|
| Django model drift | Document which models are read-only ORM vs which need gRPC sync |
|
|
| Multiple air instances conflict | The `flock` mechanism in wallet Makefile handles this |
|
|
|
|
## 7. Non-Goals
|
|
|
|
- Changing protobuf definitions — `.proto` files stay as-is
|
|
- Adding new frameworks — no ORM replacement
|
|
- Rewriting the UI
|
|
- Complete test suite rewrite
|