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...
Expert guidance for Brokle's Domain-Driven Design (DDD) architecture.
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
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
// 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"
)
// 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")
)
// 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)
}
// 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)
}
NOT all entities have organization_id - scoping depends on entity type:
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
}
// 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
}
// 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"`
}
// 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:
internal/core/domain/organization/organization.go:54-73internal/core/domain/auth/auth.go:94internal/core/domain/auth/auth.go:113-114internal/core/domain/user/user.go:14-39// 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
}
mkdir -p internal/core/domain/my-domain
touch internal/core/domain/my-domain/{entities,repository,service,errors,types}.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
}
// errors.go
package mydomain
import "errors"
var (
ErrNotFound = errors.New("entity not found")
ErrAlreadyExists = errors.New("entity already exists")
ErrInvalidInput = errors.New("invalid input")
)
// 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
}
// 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)
}
In internal/core/services/my-domain/
In internal/infrastructure/repository/my-domain/
In internal/app/app.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)
}
internal/core/domain/ for patternsCLAUDE.md - Architecture overviewdocs/development/PATTERNS.md - Domain patterns