Files
dev-procfile/REFACTORING-PLAN.md
T

14 KiB

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 figkoanf 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_migartionno_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:

# 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:

# 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