How do I handle cross-cutting concerns in EK9?
← Design Patterns and Idioms · Ref: Q266
EK9 handles cross-cutting concerns using the decorator pattern via trait delegation. Each decorator wraps a service, adds behavior before or after, then delegates to the wrapped service.
SERVICE TRAIT
Define the service contract as a trait:
Service
execute() as abstract
-> request as String
<- rtn as String?
CORE IMPLEMENTATION
The base service handles the actual work:
CoreService with trait of Service override execute() -> request as String <- rtn as String: "Processed: " + request
DECORATOR VIA DELEGATION
Use trait delegation with by to create decorators:
LoggingService with trait of Service by delegate delegate as Service
Override execute() to add logging before and after delegation.
STACKING DECORATORS
Wrap decorators around each other:
core <- CoreService() logged <- LoggingService(core) validated <- ValidatingService(logged)
Calling validated.execute() runs: validation then logging then core.
ORDER MATTERS
The outermost decorator runs first. Stack in the order you want execution:
ValidatingService(LoggingService(CoreService()))
This validates first, then logs, then processes.
VS AOP FRAMEWORKS
No separate AOP framework needed. Trait delegation with selective override provides the same cross-cutting capability with explicit, debuggable code.
See Q106 for trait basics. See Q115 for dynamic classes as lightweight decorators. See Q210 for trait delegation with by. See Q213 for mutex lock as a cross-cutting thread safety pattern.
See Q337 for transaction aspects. See Q334 for transaction delegation.
Example
defines module qa.patterns.crosscutting defines trait Service execute() as abstract -> request as String <- rtn as String? defines class CoreService with trait of Service override execute() -> request as String <- rtn as String: "Processed: " + request default operator ? // Decorator: adds logging around the delegated call LoggingService with trait of Service by delegate delegate as Service: CoreService() LoggingService() -> svc as Service this.delegate: svc override execute() -> request as String <- rtn as String? Stdout().println("[LOG] Before: " + request) rtn: delegate.execute(request) Stdout().println("[LOG] After: " + $rtn) default operator ? // Decorator: adds validation before the delegated call ValidatingService with trait of Service by delegate delegate as Service: CoreService() ValidatingService() -> svc as Service this.delegate: svc override execute() -> request as String <- rtn as String: String() if request? rtn: delegate.execute(request) default operator ? defines program CrossCuttingDemo() stdout <- Stdout() // === CORE SERVICE ALONE === core <- CoreService() stdout.println(core.execute("plain")) // === SINGLE DECORATOR === logged <- LoggingService(core) stdout.println(logged.execute("with-logging")) // === STACKED DECORATORS === // Validation runs first, then logging, then core stacked <- ValidatingService(LoggingService(CoreService())) stdout.println(stacked.execute("fully-decorated")) stdout.println("Cross-cutting via decorator stacking")
Common mistakes
E05120 — When overriding a trait method in a delegating class, the 'override' keyword is required. LoggingService and ValidatingService must use 'override execute()' to customize the delegated Service method. See ek9 -h E05120 for details.
Incorrect:
execute()
Correct:
override execute()
E08180 — Class fields must be initialized inline with a default value. Declaring a field without an initializer triggers E08180. Provide a default like 'CoreService()' even if a constructor will override it. See ek9 -h E08180 for details.
Incorrect:
delegate as Service
Correct:
delegate as Service: CoreService()
Other ways to ask this
- How do I add logging without modifying existing code?
- How do I implement the decorator pattern in EK9?
- How do I stack multiple behaviors in EK9?
Coming from another language?
Java: AOP frameworks (Spring AOP, AspectJ), proxy-based decorators, annotation-driven aspects. Python: decorators (@decorator syntax), metaclasses, monkey patching. Rust: no built-in AOP, middleware pattern, tower crate for service layers. Go: middleware functions, handler wrapping. Kotlin: delegated properties, class delegation with by. EK9: trait delegation with by for decorator stacking, selective override for cross-cutting behavior, no AOP framework needed.
Keywords: advice, cross-cutting, middleware, delegate, logging, concern, design, idiom, pattern, wrap, stack, aspect, validation, decorator, weave