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