174 lines
5.7 KiB
Markdown
174 lines
5.7 KiB
Markdown
|
|
# BOC v2 — Architecture Proposal
|
||
|
|
|
||
|
|
## Nuvarande Problem (v1.0)
|
||
|
|
- 13K rader Go i ett enda monolit-paket
|
||
|
|
- 20 handlers i samma package, delar `*sql.DB`
|
||
|
|
- Ingen repository pattern — SQL direkt i handlers
|
||
|
|
- Ingen service layer — affärslogik i HTTP-handlers
|
||
|
|
- Ledger-integration är en pass-through proxy
|
||
|
|
- Ingen event sourcing trots Kafka-definitioner
|
||
|
|
- Frontend: 12 HTML-filer med copy-paste
|
||
|
|
|
||
|
|
## V2 Vision: Clean Architecture
|
||
|
|
|
||
|
|
```
|
||
|
|
┌─────────────────────────────────────────┐
|
||
|
|
│ Transport (HTTP / WebSocket / CLI) │
|
||
|
|
│ - handlers/ chi routers │
|
||
|
|
│ - middleware/ auth, cors, rate │
|
||
|
|
│ - dto/ request/response │
|
||
|
|
├─────────────────────────────────────────┤
|
||
|
|
│ Application (Use Cases) │
|
||
|
|
│ - services/ business logic │
|
||
|
|
│ - commands/ CQRS write │
|
||
|
|
│ - queries/ CQRS read │
|
||
|
|
├─────────────────────────────────────────┤
|
||
|
|
│ Domain (Core Business) │
|
||
|
|
│ - models/ entities, value obj │
|
||
|
|
│ - events/ domain events │
|
||
|
|
│ - repositories/ interfaces │
|
||
|
|
├─────────────────────────────────────────┤
|
||
|
|
│ Infrastructure │
|
||
|
|
│ - db/ PostgreSQL impl │
|
||
|
|
│ - cache/ Redis impl │
|
||
|
|
│ - events/ Kafka impl │
|
||
|
|
│ - email/ Resend impl │
|
||
|
|
│ - pdf/ gofpdf impl │
|
||
|
|
│ - ledger/ aamos-ledger client │
|
||
|
|
└─────────────────────────────────────────┘
|
||
|
|
```
|
||
|
|
|
||
|
|
## V2 Förändringar
|
||
|
|
|
||
|
|
### 1. Repository Pattern
|
||
|
|
```go
|
||
|
|
// domain/repositories/customer.go
|
||
|
|
type CustomerRepository interface {
|
||
|
|
FindByID(ctx context.Context, id uuid.UUID) (*models.Customer, error)
|
||
|
|
FindByTenant(ctx context.Context, tenantID uuid.UUID, opts ListOptions) ([]*models.Customer, error)
|
||
|
|
Create(ctx context.Context, c *models.Customer) error
|
||
|
|
Update(ctx context.Context, c *models.Customer) error
|
||
|
|
Delete(ctx context.Context, id uuid.UUID) error
|
||
|
|
}
|
||
|
|
|
||
|
|
// infrastructure/db/customer_repo.go
|
||
|
|
type PostgresCustomerRepo struct { db *sql.DB }
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Service Layer (Transactions)
|
||
|
|
```go
|
||
|
|
// application/services/quote_service.go
|
||
|
|
func (s *QuoteService) ConvertToOrder(ctx context.Context, quoteID uuid.UUID) (*models.Order, error) {
|
||
|
|
return s.db.WithTx(ctx, func(tx *sql.Tx) error {
|
||
|
|
quote, err := s.quotes.FindByIDTx(ctx, tx, quoteID)
|
||
|
|
if err != nil { return err }
|
||
|
|
|
||
|
|
order := quote.ToOrder()
|
||
|
|
if err := s.orders.CreateTx(ctx, tx, order); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
quote.Status = models.QuoteConverted
|
||
|
|
return s.quotes.UpdateTx(ctx, tx, quote)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. Domain Events (Kafka aktiverad)
|
||
|
|
```go
|
||
|
|
// domain/events/customer_events.go
|
||
|
|
type CustomerCreated struct {
|
||
|
|
CustomerID uuid.UUID
|
||
|
|
TenantID uuid.UUID
|
||
|
|
Email string
|
||
|
|
}
|
||
|
|
|
||
|
|
// application/event_publisher.go
|
||
|
|
func (p *KafkaPublisher) Publish(ctx context.Context, event domain.Event) error {
|
||
|
|
// Actually uses Kafka now, not just defined
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. Ledger Integration med Circuit Breaker
|
||
|
|
```go
|
||
|
|
// infrastructure/ledger/client.go
|
||
|
|
type LedgerClient struct {
|
||
|
|
baseURL string
|
||
|
|
httpClient *http.Client
|
||
|
|
circuitBreaker *gobreaker.CircuitBreaker
|
||
|
|
cache cache.Cache
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *LedgerClient) GetBalanceSheet(ctx context.Context) (*BalanceSheet, error) {
|
||
|
|
// Cache-first, circuit breaker, fallback to stale data
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 5. CQRS för Analytics
|
||
|
|
```go
|
||
|
|
// application/queries/dashboard_query.go
|
||
|
|
type DashboardQuery struct {
|
||
|
|
readDB *sql.DB // Read replica or materialized view
|
||
|
|
}
|
||
|
|
|
||
|
|
func (q *DashboardQuery) GetKPIs(ctx context.Context, tenantID uuid.UUID) (*KPIs, error) {
|
||
|
|
// Optimized read query, no business logic
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## V2 Teknisk Stack
|
||
|
|
|
||
|
|
| Komponent | Nu | V2 |
|
||
|
|
|-----------|-----|-----|
|
||
|
|
| Router | chi | chi (behåll) |
|
||
|
|
| DB | database/sql | sqlx eller pgx |
|
||
|
|
| Migrations | custom | golang-migrate |
|
||
|
|
| Validation | manual | go-playground/validator |
|
||
|
|
| Testing | testify | testify + sqlmock + dockertest |
|
||
|
|
| Events | Kafka stub | Kafka aktiverad |
|
||
|
|
| Cache | Redis wrapper | Redis + cache-aside pattern |
|
||
|
|
| Frontend | 12 HTML | Vanilla JS SPA (se FRONTEND_REFACTOR_PROPOSAL.md) |
|
||
|
|
|
||
|
|
## V2 Migreringsplan
|
||
|
|
|
||
|
|
### Fas 1: Foundation (1 vecka)
|
||
|
|
1. Refactor till Clean Architecture packages
|
||
|
|
2. Implementera Repository pattern för CRM + Sales
|
||
|
|
3. Lägg till service layer med transaktioner
|
||
|
|
4. Riktiga integrationstester med dockertest
|
||
|
|
|
||
|
|
### Fas 2: Events + Cache (1 vecka)
|
||
|
|
1. Aktivera Kafka publishing från services
|
||
|
|
2. Implementera cache-aside för analytics
|
||
|
|
3. Circuit breaker för ledger
|
||
|
|
|
||
|
|
### Fas 3: Frontend (3 dagar)
|
||
|
|
1. Vanilla JS SPA shell
|
||
|
|
2. Konvertera moduler en i taget
|
||
|
|
3. Ta bort gamla HTML-filer
|
||
|
|
|
||
|
|
### Fas 4: Polish (2 dagar)
|
||
|
|
1. OpenAPI/Swagger docs
|
||
|
|
2. Health checks för alla dependencies
|
||
|
|
3. Metrics (Prometheus)
|
||
|
|
4. Structured logging med trace IDs
|
||
|
|
|
||
|
|
## V2 "Inte Nu"
|
||
|
|
- GraphQL (YAGNI)
|
||
|
|
- Microservices (för tidigt)
|
||
|
|
- Kubernetes operators (overkill)
|
||
|
|
- React/Vue (för tungt)
|
||
|
|
|
||
|
|
## Sammanfattning
|
||
|
|
|
||
|
|
V2 handlar inte om nya features. V2 handlar om att **det vi har faktiskt fungerar pålitligt**.
|
||
|
|
|
||
|
|
Nuvarande v1.0 är en demo som ser komplett ut men har:
|
||
|
|
- Säkerhetshål (fixade idag)
|
||
|
|
- Tysta databasfel (fixade idag)
|
||
|
|
- Ingen transaktionssäkerhet
|
||
|
|
- Död kod (Rust, C, Kafka)
|
||
|
|
- Noll testtäckning
|
||
|
|
|
||
|
|
V2 = produktionsklar.
|