UI → Use Cases → Domain ← Infrastructure
Rules:
- Domain has no external dependencies
- Use cases orchestrate domain logic
- Infrastructure implements domain interfaces
- UI communicates through use cases
- Dependencies point inward
// Domain trait
pub trait TaskRepository: Send + Sync {
async fn create(&self, task: Task) -> Result<Task>;
async fn find_by_id(&self, id: TaskId) -> Result<Option<Task>>;
}
// Infrastructure implementation
pub struct SqliteTaskRepository {
pool: Arc<Mutex<SqliteConnection>>,
}
impl TaskRepository for SqliteTaskRepository {
// Implementation
}// Domain service
pub trait TimerService: Send + Sync {
async fn start(&self) -> Result<()>;
async fn stop(&self) -> Result<()>;
}
// Use case using service
pub struct StartTimerSession {
timer_service: Arc<dyn TimerService>,
event_publisher: Arc<dyn EventPublisher>,
}Task::builder()
.title("My Task")
.description("Description")
.status(TaskStatus::Pending)
.build()#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskCreated {
pub task_id: TaskId,
pub title: String,
pub timestamp: Timestamp,
}
impl Event for TaskCreated {
fn event_type(&self) -> &'static str {
"task.created"
}
}// In domain operation
self.event_publisher.publish(Box::new(TaskCreated {
task_id: task.id,
title: task.title.clone(),
timestamp: Timestamp::now(),
})).await?;pub struct TaskCreatedHandler {
notification_service: Arc<dyn NotificationService>,
}
impl EventHandler for TaskCreatedHandler {
async fn handle(&self, event: Box<dyn Event>) -> Result<()> {
if let Some(created) = event.downcast_ref::<TaskCreated>() {
// Handle event
}
Ok(())
}
}#[derive(Debug, thiserror::Error)]
pub enum TimerError {
#[error("Invalid state transition: {0}")]
InvalidTransition(String),
#[error("Timer not running")]
NotRunning,
}pub type Result<T> = std::result::Result<T, Error>;
pub type TimerResult<T> = std::result::Result<T, TimerError>;// Use ? operator
let task = self.repository.find_by_id(id).await?;
// Map errors
task.ok_or_else(|| Error::NotFound("Task not found".into()))?#[cfg(test)]
pub struct TaskBuilder {
title: String,
status: TaskStatus,
}
impl TaskBuilder {
pub fn with_title(mut self, title: &str) -> Self {
self.title = title.to_string();
self
}
}#[cfg(test)]
pub struct MockTaskRepository {
tasks: Arc<Mutex<HashMap<TaskId, Task>>>,
}
impl TaskRepository for MockTaskRepository {
// Mock implementation
}pub fn create_test_task() -> Task {
Task::builder()
.title("Test Task")
.build()
}
pub fn create_test_config() -> Config {
Config::default()
}#[tokio::test]
async fn test_complete_workflow() {
// Arrange
let context = TestContext::new().await;
// Act
let result = context.create_task("Test").await;
// Assert
assert!(result.is_ok());
}#[async_trait]
pub trait TaskRepository {
async fn find_all(&self) -> Result<Vec<Task>>;
}// Spawning tasks
tokio::spawn(async move {
// Async work
});
// Timeouts
tokio::time::timeout(Duration::from_secs(5), async_operation).await?;pub struct AppState {
timer: Arc<Mutex<Timer>>,
repository: Arc<dyn TaskRepository>,
}#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskId(Uuid);
impl TaskId {
pub fn new() -> Self {
Self(Uuid::new_v4())
}
}pub struct EntityId<T> {
id: Uuid,
_marker: PhantomData<T>,
}pub struct Timer<S> {
state: S,
}
pub struct Idle;
pub struct Running;
impl Timer<Idle> {
pub fn start(self) -> Timer<Running> {
// Transition
}
}- Snake case:
timer_service.rs - Module folders:
timer/mod.rs - Tests:
timer_tests.rs
// Structs: PascalCase
pub struct TaskRepository;
// Enums: PascalCase
pub enum TaskStatus {
Pending,
InProgress,
}
// Traits: PascalCase
pub trait EventPublisher;
// Functions: snake_case
pub fn create_task() {}
// Constants: SCREAMING_SNAKE_CASE
pub const MAX_RETRIES: u32 = 3;// Module declaration
pub mod timer;
pub mod task;
// Use statements
use domain::timer::Timer;
use crate::adapters::task;- Format:
entity.action - Examples:
task.created,timer.started
// Infrastructure DTOs for serialization
#[derive(Serialize, Deserialize)]
pub struct TaskDto {
pub id: String,
pub title: String,
}
// Conversion
impl From<Task> for TaskDto {
fn from(task: Task) -> Self {
Self {
id: task.id.to_string(),
title: task.title,
}
}
}#[tauri::command]
pub async fn create_task(
title: String,
state: State<'_, AppState>,
) -> Result<TaskDto> {
// Implementation
}create- Create new entityupdate- Update existingdelete- Remove entityfind_by_id- Single lookupfind_all- Get allsearch- Query with filters
impl Default for Config {
fn default() -> Self {
Self {
general: GeneralConfig::default(),
audio: AudioConfig::default(),
}
}
}pub struct EffectiveSettings {
work_duration: Duration,
break_duration: Duration,
}
impl EffectiveSettings {
pub fn resolve(task_settings: Option<TaskSettings>, defaults: TaskDefaults) -> Self {
// Merge logic
}
}pub struct CreateTaskUseCase {
repository: Arc<dyn TaskRepository>,
event_publisher: Arc<dyn EventPublisher>,
}
impl CreateTaskUseCase {
pub fn new(
repository: Arc<dyn TaskRepository>,
event_publisher: Arc<dyn EventPublisher>,
) -> Self {
Self { repository, event_publisher }
}
}// infra/src/bootstrap.rs
pub async fn create_app_state() -> AppState {
let repository = Arc::new(SqliteTaskRepository::new(pool));
let event_publisher = Arc::new(MemEventBus::new());
AppState {
repository,
event_publisher,
}
}