The Domain Layer contains full bounded contexts with business logic, repositories, and services. Each domain crate owns its database tables and defines its public API.

Characteristics

  • Contains business logic - Domain rules and workflows
  • Owns database tables - SQL schemas in the crate
  • Repository pattern - Data access abstraction
  • Event-driven - Communicates via events
  • Clear public API - Well-defined boundaries

Crates

Crate Purpose
systemprompt-users User management, roles, permissions
systemprompt-oauth OAuth2/OIDC flows, token management
systemprompt-files File storage, metadata, serving
systemprompt-analytics Metrics, usage tracking, reports
systemprompt-content Markdown, publishing, search
systemprompt-mcp MCP server registry, tool execution
systemprompt-ai LLM providers, request logging
systemprompt-agent A2A protocol, agent registry
systemprompt-templates Template registry, rendering

Required Structure

Every domain crate follows this structure:

domain/{name}/
├── Cargo.toml
├── schema/               # SQL files
│   ├── {table}.sql
│   └── migrations/
├── src/
│   ├── lib.rs           # Public API
│   ├── extension.rs     # Extension registration
│   ├── models/          # Domain types
│   │   ├── mod.rs
│   │   └── {entity}.rs
│   ├── repository/      # Data access
│   │   ├── mod.rs
│   │   └── {entity}_repository.rs
│   └── services/        # Business logic
│       ├── mod.rs
│       └── {service}.rs

Dependency Rules

Domain layer crates:

  • Can depend on Shared layer crates
  • Can depend on Infrastructure layer crates
  • Can depend on other Domain crates (with care)
  • Cannot depend on Application or Entry crates

Cross-Domain Communication

Domain crates should communicate via events, not direct dependencies:

// In users domain
events.emit(UserCreated { user_id, email }).await;

// In analytics domain (subscribes to user events)
events.subscribe::<UserCreated, _>(|event| async {
    analytics.track_signup(event.user_id).await;
});

Common Patterns

Repository Pattern

// Define the trait
pub trait UserRepository: Send + Sync {
    async fn find_by_id(&self, id: UserId) -> Result<Option<User>>;
    async fn find_by_email(&self, email: &str) -> Result<Option<User>>;
    async fn save(&self, user: &User) -> Result<()>;
    async fn delete(&self, id: UserId) -> Result<()>;
}

// Implementation
pub struct PgUserRepository {
    pool: Arc<PgPool>,
}

impl UserRepository for PgUserRepository {
    async fn find_by_id(&self, id: UserId) -> Result<Option<User>> {
        sqlx::query_as!(User,
            r#"SELECT * FROM users WHERE id = $1"#,
            id.as_uuid()
        )
        .fetch_optional(&*self.pool)
        .await
        .map_err(Into::into)
    }
}

Service Layer

pub struct UserService {
    repository: Arc<dyn UserRepository>,
    events: Arc<EventBus>,
}

impl UserService {
    pub async fn create_user(&self, input: CreateUserInput) -> Result<User> {
        // Validation
        input.validate()?;

        // Check uniqueness
        if self.repository.find_by_email(&input.email).await?.is_some() {
            return Err(UserError::EmailExists);
        }

        // Create user
        let user = User::new(input);
        self.repository.save(&user).await?;

        // Emit event
        self.events.emit(UserCreated {
            user_id: user.id,
            email: user.email.clone(),
        }).await;

        Ok(user)
    }
}

Layer Diagram

┌────────────────────────────────────────────────────────────┐
│                      DOMAIN LAYER                          │
│                                                            │
│  ┌───────┐  ┌───────┐  ┌───────┐  ┌───────────┐          │
│  │ users │  │ oauth │  │ files │  │ analytics │          │
│  └───────┘  └───────┘  └───────┘  └───────────┘          │
│                                                            │
│  ┌─────────┐  ┌─────┐  ┌────┐  ┌───────┐  ┌───────────┐  │
│  │ content │  │ mcp │  │ ai │  │ agent │  │ templates │  │
│  └─────────┘  └─────┘  └────┘  └───────┘  └───────────┘  │
│                                                            │
│                         ↓                                  │
│        Depends on INFRASTRUCTURE and SHARED                │
└────────────────────────────────────────────────────────────┘