Claude Code Plugins

Community-maintained marketplace

Feedback

brokle-domain-architecture

@brokle-ai/brokle
2
0

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.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name brokle-domain-architecture
description 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.

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, observations, 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

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

// 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

// 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

// 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

// 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)

// 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)

// 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)

// 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)

// 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

// 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

mkdir -p internal/core/domain/my-domain
touch internal/core/domain/my-domain/{entities,repository,service,errors,types}.go

Step 2: Define Entities

// 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

// 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

// 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

// 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

// 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