backbone_core/domain_service.rs
1//! Domain Service Trait
2//!
3//! Base trait for all domain services in the Backbone Framework.
4//! Domain services encapsulate business logic that doesn't naturally
5//! belong to a single entity.
6//!
7//! # Example
8//!
9//! ```ignore
10//! use backbone_core::DomainService;
11//!
12//! pub struct PaymentService {
13//! // dependencies
14//! }
15//!
16//! #[async_trait::async_trait]
17//! impl DomainService for PaymentService {
18//! fn service_id(&self) -> &'static str {
19//! "payment.payment_service"
20//! }
21//!
22//! async fn health_check(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
23//! // Check payment gateway connectivity
24//! Ok(())
25//! }
26//! }
27//! ```
28
29use async_trait::async_trait;
30
31/// Base trait for all domain services.
32///
33/// Domain services are stateless operations that coordinate between
34/// multiple entities or handle complex business logic.
35#[async_trait]
36pub trait DomainService: Send + Sync {
37 /// Unique identifier for this service.
38 ///
39 /// Format: `{module}.{service_name}`
40 /// Example: `"corpus.organization_service"`
41 fn service_id(&self) -> &'static str;
42
43 /// Perform a health check for this service.
44 ///
45 /// Returns `Ok(())` if the service is healthy,
46 /// or an error describing the issue.
47 async fn health_check(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
48 Ok(())
49 }
50
51 /// Get the service name (derived from service_id).
52 fn service_name(&self) -> &'static str {
53 self.service_id()
54 .split('.')
55 .next_back()
56 .unwrap_or(self.service_id())
57 }
58
59 /// Get the module name (derived from service_id).
60 fn module_name(&self) -> &'static str {
61 self.service_id()
62 .split('.')
63 .next()
64 .unwrap_or("unknown")
65 }
66}
67
68/// Marker trait for domain services that support transactions.
69#[async_trait]
70pub trait TransactionalDomainService: DomainService {
71 /// Execute an operation within a transaction context.
72 async fn with_transaction<F, R, E>(&self, operation: F) -> Result<R, E>
73 where
74 F: FnOnce() -> Result<R, E> + Send,
75 R: Send,
76 E: Send;
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 struct TestService;
84
85 #[async_trait]
86 impl DomainService for TestService {
87 fn service_id(&self) -> &'static str {
88 "test.test_service"
89 }
90 }
91
92 #[test]
93 fn test_service_name_extraction() {
94 let service = TestService;
95 assert_eq!(service.service_name(), "test_service");
96 assert_eq!(service.module_name(), "test");
97 }
98}