Scala's Cake Pattern, One Dependency at a Time
I know dependency injection from Java and Spring: a service declares constructor parameters, the container supplies implementations, and tests replace those implementations. Scala's Cake Pattern expresses the same architectural concern through traits, abstract members and self-types. I wanted to understand the mechanics without hiding them inside a framework, so I built a small checkout application with Scala 2.13.18 and sbt.
The finished example is available in the public cackepattern repository. It stores an order, sends a confirmation and returns a receipt. The domain is deliberately ordinary; the interesting part is how the pieces are wired.
Start with one component that provides something
The first slice describes order storage:
trait OrderRepositoryComponent {
def orderRepository: OrderRepository
trait OrderRepository {
def save(order: Order): Unit
def find(orderId: String): Option[Order]
}
}
The inner OrderRepository trait is the familiar contract. It says what storage can do without choosing a database or collection. The outer OrderRepositoryComponent adds another promise: any completed version of this component must provide an orderRepository instance.
That distinction was the first point I needed to make precise. The component does not consume a repository dependency. It provides a named slot for one. A concrete slice fulfills the slot:
trait InMemoryOrderRepositoryComponent
extends OrderRepositoryComponent {
private val orders = mutable.Map.empty[String, Order]
override lazy val orderRepository: OrderRepository =
new OrderRepository {
override def save(order: Order): Unit =
orders.update(order.id, order)
override def find(orderId: String): Option[Order] =
orders.get(orderId)
}
}
The abstract parameterless def is satisfied by a lazy val. The value is created on first access and the same repository object is returned afterwards. The val reference cannot be reassigned, although the map held behind it remains mutable.
The self-type states what checkout needs
The dependency relationship appears in the checkout component:
trait CheckoutComponent {
self: OrderRepositoryComponent with NotificationComponent =>
def checkoutService: CheckoutService
// ...
}
I read the self-type as a compile-time requirement: checkout may only be mixed into something that also contains repository and notification components. It can therefore call orderRepository.save and notificationService.orderConfirmed even though it implements neither service.
This is different from writing extends OrderRepositoryComponent. Checkout is not claiming to be a repository implementation. It is saying that it must be assembled beside one. The Scala documentation describes self-types as a way to require that a trait be mixed into another trait without directly extending it.

If I try to build an application with checkout and the repository but omit notifications, compilation fails. The wiring error cannot wait until a request reaches production.
One object assembles the cake
The final application mixes the three slices:
object Application
extends CheckoutComponent
with InMemoryOrderRepositoryComponent
with ConsoleNotificationComponent {
override lazy val checkoutService: CheckoutService =
new LiveCheckoutService
}
Application is a Scala singleton object. It contains one checkout service, one repository and one notification service. The self-type is satisfied because the two required component types are present. There is no classpath scan, reflection or Spring application context involved in this example.
Trace one order through the assembled object
The checkout implementation is small enough to follow literally:
override def checkout(order: Order): Receipt = {
require(order.totalInCents > 0, "order total must be positive")
orderRepository.save(order)
notificationService.orderConfirmed(order)
Receipt(order.id, s"Order ${order.id} checked out successfully")
}
An order with a total of 4,299 cents passes validation, enters the in-memory map, prints its confirmation and produces a receipt. A zero total throws IllegalArgumentException at require, before either side effect executes.

The test builds a different cake
The test application mixes the same CheckoutComponent with small repository and notification implementations backed by ListBuffer. Those buffers record saved and notified orders. The production checkout logic remains unchanged.
Two tests exercise the useful boundaries: a valid order must be saved and notified, while a non-positive total must invoke neither dependency. The verified build result was:
Total number of tests run: 2
Tests: succeeded 2, failed 0
All tests passed.
Where I would use it
The Cake Pattern is worth understanding because established Scala 2 codebases use it, and it demonstrates how far the type system and trait composition can take dependency wiring. It can also suit an application that deliberately wants static assembly without a dependency-injection framework.
I would not make it the automatic choice for a small service. Constructor parameters usually expose dependencies more directly. Large cakes can introduce initialization-order problems, path-dependent types and wiring that takes longer to navigate. In this sample, lazy val helps make initialization explicit, but it does not erase those broader tradeoffs.
The useful mental model is compact: a component provides something, a self-type requires components, and the final object assembles the complete application. The cake metaphor is optional; the compiler-enforced dependency graph is the real pattern.
Run the sample
git clone https://github.com/zakariahere/cackepattern.git
cd cackepattern
sbt run
sbt test
The repository pins Scala 2.13.18 and sbt 1.12.15. The complete source, tests and illustrated README are available on GitHub. For the language rule behind the central line, the official Scala documentation has a concise reference on self-types.