# Brokle Domain Architecture

> Use this skill when working with Brokle's domain-driven architecture, including creating new domains, modifying domain entities, designing cross-domain interactions, refactoring domain boundaries, or implementing complex domain logic. This is a specialized architectural skill.

- Skill: `majiayu000/brokle-domain-architecture` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds add majiayu000/brokle-domain-architecture`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/brokle-domain-architecture/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/majiayu000/brokle-domain-architecture

---


# Brokle Domain Architecture Skill

Expert guidance for Brokle's Domain-Driven Design (DDD) architecture.

## Domains

Primary domains in `internal/core/domain/`:

| Domain | Purpose |
|--------|---------|
| auth | Authentication, sessions, API keys |
| billing | Usage tracking, subscriptions |
| common | Shared transaction patterns, utilities |
| gateway | AI provider routing |
| observability | Traces, spans, quality scores |
| organization | Multi-tenant org management |
| user | User management and profiles |

**Structure**: Each domain has entities.go, repository.go, service.go, errors.go, types.go
**Reference**: List domains with `ls -1 internal/core/domain/` to see current implementation status

## Domain Layer Structure

```go
internal/core/domain/{domain}/
├── entities.go          # Domain entities
├── repository.go        # Repository interfaces
├── service.go           # Service interfaces
├── errors.go            # Domain-specific errors
├── types.go             # Domain types and enums
└── validators.go        # Domain validation logic
```

## Entity Pattern

```go
// internal/core/domain/auth/entities.go
package auth

import (
    "time"
    "brokle/pkg/ulid"
)

type User struct {
    ID        ulid.ULID
    Email     string
    Name      string
    Status    UserStatus
    Role      Role
    CreatedAt time.Time
    UpdatedAt time.Time
}

// Domain enums
type UserStatus string
const (
    UserStatusActive   UserStatus = "active"
    UserStatusInactive UserStatus = "inactive"
    UserStatusSuspended UserStatus = "suspended"
)

type Role string
const (
    RoleOwner  Role = "owner"
    RoleAdmin  Role = "admin"
    RoleUser   Role = "user"
    RoleViewer Role = "viewer"
)
```

## Domain Errors

```go
// internal/core/domain/auth/errors.go
package auth

import "errors"

var (
    ErrNotFound           = errors.New("user not found")
    ErrAlreadyExists      = errors.New("user already exists")
    ErrInvalidCredentials = errors.New("invalid credentials")
    ErrSessionExpired     = errors.New("session expired")
)
```

## Repository Interfaces

```go
// internal/core/domain/auth/repository.go
package auth

import (
    "context"
    "brokle/pkg/ulid"
)

type UserRepository interface {
    Create(ctx context.Context, user *User) error
    GetByID(ctx context.Context, id ulid.ULID) (*User, error)
    GetByEmail(ctx context.Context, email string) (*User, error)
    Update(ctx context.Context, user *User) error
    Delete(ctx context.Context, id ulid.ULID) error
    List(ctx context.Context, filter UserFilter) ([]*User, error)
}
```

## Service Interfaces

```go
// internal/core/domain/auth/service.go
package auth

import "context"

type AuthService interface {
    Register(ctx context.Context, req *RegisterRequest) (*RegisterResponse, error)
    Login(ctx context.Context, req *LoginRequest) (*LoginResponse, error)
    Logout(ctx context.Context, token string) error
    ValidateSession(ctx context.Context, token string) (*User, error)
}
```

## Multi-Tenant Scoping Patterns

**NOT all entities have `organization_id`** - scoping depends on entity type:

### 1. Organization-Scoped Entities (Direct `organization_id`)
```go
// organization/organization.go:54-57
type Project struct {
    ID             ulid.ULID
    OrganizationID ulid.ULID `json:"organization_id" gorm:"type:char(26);not null"`
    Name           string
}

// organization/organization.go:37-39
type Member struct {
    OrganizationID ulid.ULID `json:"organization_id" gorm:"type:char(26);not null;primaryKey"`
    UserID         ulid.ULID
}
```

### 2. Project-Scoped Entities (Organization via Project)
```go
// auth/auth.go:94 - APIKey is project-scoped
type APIKey struct {
    ID        ulid.ULID
    ProjectID ulid.ULID `json:"project_id" gorm:"type:char(26);not null;index"`
    // Organization derived via Project join
}
```

### 3. Scoped Entities (Flexible Scoping)
```go
// auth/auth.go:113-114 - Role uses flexible scope_type pattern
type Role struct {
    ID        ulid.ULID
    ScopeType string     `json:"scope_type" gorm:"size:20;not null"`  // "organization", "project", "global"
    ScopeID   *ulid.ULID `json:"scope_id,omitempty" gorm:"type:char(26);index"`
}
```

### 4. Global Entities (No organization_id)
```go
// user/user.go:14-39 - User is global with optional org reference
type User struct {
    ID    ulid.ULID
    Email string
    DefaultOrganizationID *ulid.ULID `json:"default_organization_id,omitempty" gorm:"type:char(26)"`
    // NOT required - users can belong to multiple orgs via Member table
}

// organization/organization.go:16 - Organization IS the tenant
type Organization struct {
    ID   ulid.ULID
    Name string
    // No organization_id - it IS the organization
}
```

**Reference Files**:
- Organization-scoped: `internal/core/domain/organization/organization.go:54-73`
- Project-scoped: `internal/core/domain/auth/auth.go:94`
- Scoped (flexible): `internal/core/domain/auth/auth.go:113-114`
- Global: `internal/core/domain/user/user.go:14-39`

## Cross-Domain Relationships

```go
// Example: Organization domain referencing User domain
package organization

import (
    userDomain "brokle/internal/core/domain/user"
)

type Member struct {
    ID             ulid.ULID
    OrganizationID ulid.ULID
    UserID         ulid.ULID  // References user domain
    Role           string
    Status         MemberStatus
}

// Service can accept interfaces from other domains
type OrganizationService struct {
    orgRepo    OrganizationRepository
    userRepo   userDomain.UserRepository  // Cross-domain dependency
    memberRepo MemberRepository
}
```

## Creating a New Domain

### Step 1: Create Domain Structure

```bash
mkdir -p internal/core/domain/my-domain
touch internal/core/domain/my-domain/{entities,repository,service,errors,types}.go
```

### Step 2: Define Entities

```go
// entities.go
package mydomain

import (
    "time"
    "brokle/pkg/ulid"
)

type MyEntity struct {
    ID             ulid.ULID
    OrganizationID ulid.ULID  // Always include for multi-tenancy
    Name           string
    Status         MyStatus
    CreatedAt      time.Time
    UpdatedAt      time.Time
}
```

### Step 3: Define Domain Errors

```go
// errors.go
package mydomain

import "errors"

var (
    ErrNotFound      = errors.New("entity not found")
    ErrAlreadyExists = errors.New("entity already exists")
    ErrInvalidInput  = errors.New("invalid input")
)
```

### Step 4: Define Repository Interface

```go
// repository.go
package mydomain

import (
    "context"
    "brokle/pkg/ulid"
)

type MyEntityRepository interface {
    Create(ctx context.Context, entity *MyEntity) error
    GetByID(ctx context.Context, id ulid.ULID) (*MyEntity, error)
    Update(ctx context.Context, entity *MyEntity) error
    Delete(ctx context.Context, id ulid.ULID) error
}
```

### Step 5: Define Service Interface

```go
// service.go
package mydomain

import "context"

type MyDomainService interface {
    CreateEntity(ctx context.Context, req *CreateEntityRequest) (*CreateEntityResponse, error)
    GetEntity(ctx context.Context, id ulid.ULID) (*GetEntityResponse, error)
}
```

### Step 6: Implement Service

In `internal/core/services/my-domain/`

### Step 7: Implement Repository

In `internal/infrastructure/repository/my-domain/`

### Step 8: Register in DI Container

In `internal/app/app.go`

## Domain Validation

```go
// validators.go
package auth

import (
    "errors"
    "regexp"
)

func (u *User) Validate() error {
    if u.Email == "" {
        return errors.New("email is required")
    }
    if !isValidEmail(u.Email) {
        return errors.New("invalid email format")
    }
    if u.Name == "" {
        return errors.New("name is required")
    }
    return nil
}

func isValidEmail(email string) bool {
    return regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`).MatchString(email)
}
```

## Key Principles

1. **Domain Purity**: Domain layer has no external dependencies
2. **Multi-Tenancy**: All entities scoped by organization
3. **Domain Errors**: Use domain-specific errors
4. **Validation**: Domain entities validate themselves
5. **Interfaces**: Define repository and service interfaces in domain
6. **Cross-Domain**: Use domain aliases for cross-domain references

## References

- Existing domains in `internal/core/domain/` for patterns
- `CLAUDE.md` - Architecture overview
- `docs/development/PATTERNS.md` - Domain patterns

