How do I add transactions with trait delegation in EK9?
← Dependency Injection · Ref: Q334
EK9's 'by delegate' syntax wraps a service with transaction management without modifying the original service. This is the Decorator pattern applied to transactions.
DELEGATION PATTERN
Define a trait for the service contract, then create a delegating wrapper:
TransactionalService with trait of Service by delegate delegate as Service: BasicService() override process() -> input as String <- result <- String() // wrap with transaction logic result: delegate.process(input)
HOW IT WORKS
1. 'by delegate' tells the compiler to generate delegation methods for all abstract methods
2. You override only the methods that need transaction wrapping
3. Non-overridden methods pass through directly to the delegate
4. The original service is unaware of transaction management
ADVANTAGES
1. SEPARATION OF CONCERNS: business logic and transaction management are separate
2. COMPOSABLE: multiple decorators can be stacked (logging, caching, transactions)
3. TESTABLE: test the service without transactions, test the wrapper separately
4. OPEN/CLOSED: add transactions without modifying existing service code
SIMILAR TO C# MIDDLEWARE
This pattern mirrors C#'s middleware pipeline where each layer wraps the next. But in EK9, the delegation is compile-time generated and type-safe.
See Q210 for trait delegation basics. See Q333 for try-with-resources pattern. See Q266 for cross-cutting concerns. See Q335 for callback pattern.
Example
defines module qa.di.transaction.decorator defines trait <?- Service contract as a trait (required for delegation). -?> OrderService processOrder() as abstract -> orderId as String <- result as String? cancelOrder() as abstract -> orderId as String <- result as String? defines class <?- Concrete service with business logic only. No transaction awareness. -?> BasicOrderService with trait of OrderService override processOrder() -> orderId as String <- result as String: "Processed: " + orderId override cancelOrder() -> orderId as String <- result as String: "Cancelled: " + orderId default operator ? <?- Transactional wrapper using delegation. Only processOrder gets transaction wrapping. cancelOrder passes through to delegate unchanged. -?> TransactionalOrderService with trait of OrderService by delegate delegate as OrderService: BasicOrderService() TransactionalOrderService() -> service as OrderService this.delegate: service override processOrder() -> orderId as String <- result <- String() stdout <- Stdout() stdout.println("BEGIN transaction for " + orderId) result: delegate.processOrder(orderId) stdout.println("COMMIT transaction for " + orderId) default operator ? defines program TransactionDecoratorDemo() stdout <- Stdout() // === DELEGATION: wrap service with transaction === basicService <- BasicOrderService() transactionalService <- TransactionalOrderService(basicService) // processOrder is wrapped with transaction result1 <- transactionalService.processOrder("ORD-001") stdout.println(result1) // cancelOrder passes through unchanged via delegation result2 <- transactionalService.cancelOrder("ORD-002") stdout.println(result2) stdout.println("Delegation adds transactions without modifying service")
Common mistakes
E08180 — Class fields must be initialized at declaration. The delegate field needs a default value. Only injection fields ('!') can be left uninitialized because the DI framework provides their values. See ek9 -h E08180 for details.
Incorrect:
delegate as OrderService
Correct:
delegate as OrderService: BasicOrderService()
Other ways to ask this
- How do I use the decorator pattern for transactions in EK9?
- Can I wrap a service with transaction management in EK9?
- What is trait delegation for transaction handling?
Coming from another language?
Java Spring: @Transactional on service methods, proxy-based decoration. C#: middleware pipeline, HttpClient DelegatingHandler. Go: middleware functions wrapping handlers. Rust: tower middleware layers. Python: decorators (@transactional). EK9: 'by delegate' for compile-time generated delegation, override specific methods for transaction wrapping, type-safe composition.
Keywords: virtual, wrap, inject, abstract, middleware, compose, service, delegate, dependency, separation, decorator, open, migrate, layer, override, trait, pattern