The Application Layer coordinates domain services without containing business logic itself. It handles lifecycle management, scheduling, and cross-domain workflows.

Characteristics

  • Coordinates services - Orchestrates domain crates
  • Manages lifecycle - Startup, shutdown, health checks
  • No business logic - Delegates to domain crates
  • No direct database access - Uses domain services

Crates

Crate Purpose
systemprompt-runtime AppContext, lifecycle, module registry
systemprompt-scheduler Cron scheduling, job execution
systemprompt-generator Static site generation
systemprompt-sync Cloud synchronization

Dependency Rules

Application layer crates:

  • Can depend on Shared layer crates
  • Can depend on Infrastructure layer crates
  • Can depend on Domain layer crates
  • Cannot depend on Entry layer crates

Common Patterns

AppContext

The runtime crate provides AppContext, the central coordination point:

pub struct AppContext {
    pub config: Config,
    pub database: Arc<DatabasePool>,
    pub events: Arc<EventBus>,
    pub extensions: ExtensionRegistry,
    // Domain services
    pub users: Arc<UserService>,
    pub content: Arc<ContentService>,
    pub agents: Arc<AgentService>,
    // ...
}

impl AppContext {
    pub async fn new(config: Config) -> Result<Self> {
        // Initialize infrastructure
        let database = DatabasePool::connect(&config.database).await?;
        let events = EventBus::new();

        // Discover extensions
        let extensions = ExtensionRegistry::discover();

        // Run migrations
        for ext in extensions.schema_extensions() {
            ext.migrate(&database).await?;
        }

        // Initialize domain services
        let users = UserService::new(database.clone(), events.clone());
        let content = ContentService::new(database.clone(), events.clone());

        Ok(Self { config, database, events, extensions, users, content, ... })
    }
}

Job Scheduling

use systemprompt_scheduler::{Scheduler, JobDefinition};

// Define jobs in config
let scheduler = Scheduler::new(&ctx);

// Register jobs from extensions
for ext in ctx.extensions.job_extensions() {
    for job in ext.jobs() {
        scheduler.register(job);
    }
}

// Start scheduler
scheduler.start().await;

Static Generation

use systemprompt_generator::Generator;

let generator = Generator::new(&ctx);

// Generate all content
generator.build_all().await?;

// Or generate specific content
generator.build_content(content_id).await?;

Cloud Sync

use systemprompt_sync::SyncService;

let sync = SyncService::new(&ctx);

// Sync from cloud to local
sync.pull(SyncOptions {
    source: "content",
    direction: SyncDirection::FromCloud,
}).await?;

// Sync from local to cloud
sync.push(SyncOptions {
    source: "agents",
    direction: SyncDirection::ToCloud,
}).await?;

Layer Diagram

┌────────────────────────────────────────────────────────────┐
│                    APPLICATION LAYER                        │
│                                                            │
│  ┌─────────┐  ┌───────────┐  ┌───────────┐  ┌──────┐     │
│  │ runtime │  │ scheduler │  │ generator │  │ sync │     │
│  └─────────┘  └───────────┘  └───────────┘  └──────┘     │
│                                                            │
│                         ↓                                  │
│           Depends on DOMAIN, INFRA, SHARED                 │
└────────────────────────────────────────────────────────────┘