0Pricing
Scala for Backend Engineering & Functional Programming · Lesson

Composing Domains

Build larger models.

Composing Domains is a free Scala for Backend Engineering & Functional Programming lesson on CoddyKit. This is lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Scala for Backend Engineering & Functional Programming learning path, and your progress syncs across the web and the CoddyKit app. The Scala for Backend Engineering & Functional Programming course includes 4 lessons in total.

Building Larger Models

Real domains are assembled from smaller pieces. Composing domains means combining well-modeled ADTs, newtypes, and validated values into bigger, still-correct structures.

  • Small, focused types compose into larger ones.
  • Each piece keeps its own invariants.
case class Money(cents: Long)
case class Product(name: String, price: Money)

object Main:
  def main(args: Array[String]): Unit =
    println(Product("Book", Money(1999)))

Nesting Products

Compose product types by nesting case classes. Each level adds structure while staying readable.

case class Address(city: String, zip: String)
case class Customer(name: String, address: Address)

object Main:
  def main(args: Array[String]): Unit =
    val c = Customer("Ada", Address("London", "E1"))
    println(c.address.city)

Composing with Collections

Use List, Map, and Set to model one-to-many relationships inside your domain.

case class Item(name: String, qty: Int)
case class Order(id: Int, items: List[Item])

object Main:
  def main(args: Array[String]): Unit =
    val order = Order(1, List(Item("pen", 2), Item("ink", 1)))
    println(order.items.map(_.qty).sum)

Composing Sum Types

Larger choices combine smaller ones. An enum variant can itself contain another ADT, layering decisions.

enum Payment:
  case Card(last4: String)
  case Cash

enum Checkout:
  case Completed(payment: Payment)
  case Abandoned

object Main:
  def main(args: Array[String]): Unit =
    val c: Checkout = Checkout.Completed(Payment.Card("1234"))
    println(c)

Functions That Compose Domains

Domain operations are pure functions from input types to output types. Composing them builds workflows.

case class Cart(total: Long)
case class Receipt(total: Long, tax: Long)

object Main:
  def checkout(c: Cart): Receipt =
    Receipt(c.total, (c.total * 0.2).toLong)

  def main(args: Array[String]): Unit =
    println(checkout(Cart(1000)))

Composing Validations

When each field is built by a smart constructor, compose them with a for comprehension. The whole object is created only if every part validates.

object V:
  def name(s: String): Option[String] = if s.nonEmpty then Some(s) else None
  def age(n: Int): Option[Int] = if n >= 0 then Some(n) else None

case class Person(name: String, age: Int)

object Main:
  def make(n: String, a: Int): Option[Person] =
    for nm <- V.name(n); ag <- V.age(a) yield Person(nm, ag)

  def main(args: Array[String]): Unit =
    println(Main.make("Bob", 30))
    println(Main.make("", 30))

Folding Over Composite Data

Aggregate composed structures with fold or map plus sum. The shape of the data guides the computation.

case class Line(price: Long, qty: Int)
case class Invoice(lines: List[Line])

object Main:
  def total(inv: Invoice): Long =
    inv.lines.foldLeft(0L)((acc, l) => acc + l.price * l.qty)

  def main(args: Array[String]): Unit =
    val inv = Invoice(List(Line(100, 2), Line(50, 4)))
    println(total(inv))

Updating Immutable Models

Compose changes with copy, which returns a new value with selected fields replaced. The original stays untouched.

case class Account(id: Int, balance: Long)

object Main:
  def deposit(a: Account, amt: Long): Account =
    a.copy(balance = a.balance + amt)

  def main(args: Array[String]): Unit =
    val a = Account(1, 100)
    println(deposit(a, 50))

Modeling a State Machine

Compose sum types and transition functions to model a state machine where only legal transitions exist.

enum Light:
  case Red, Green, Yellow

object Main:
  def next(l: Light): Light = l match
    case Light.Red    => Light.Green
    case Light.Green  => Light.Yellow
    case Light.Yellow => Light.Red

  def main(args: Array[String]): Unit =
    println(next(Light.Red))
    println(next(Light.Green))

Layering Domain and Operations

Keep data types pure and put behavior in functions or methods. A composed model plus a set of transformations forms a small domain library.

case class Temp(celsius: Double):
  def toF: Double = celsius * 9 / 5 + 32

object Main:
  def main(args: Array[String]): Unit =
    val t = Temp(25.0)
    println(t.toF)

Putting It Together

A complete model composes newtypes, products, sums, collections, and validated construction. The result is hard to misuse and easy to reason about.

  • Build small types first.
  • Compose into aggregates.
  • Express operations as pure functions.
case class Sku(value: String)
case class Line(sku: Sku, qty: Int)
case class Basket(lines: List[Line]):
  def count: Int = lines.map(_.qty).sum

object Main:
  def main(args: Array[String]): Unit =
    val b = Basket(List(Line(Sku("A"), 2), Line(Sku("B"), 3)))
    println(b.count)

Quick Check

Test your understanding of composing domains.

Recap

You learned to compose domains.

  • Nest products and sums to model rich structures.
  • Use collections for one-to-many relationships.
  • Compose validations with for comprehensions.
  • Update immutably with copy.
  • Keep data pure and express operations as functions.
case class User(id: Int, name: String)
case class Post(author: User, text: String)

object Main:
  def main(args: Array[String]): Unit =
    val p = Post(User(1, "Ada"), "hello")
    println(p.copy(text = "hi"))

Frequently Asked Questions

Is the “Composing Domains” lesson free?

Yes — the full text of “Composing Domains” is free to read here on the web. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Scala for Backend Engineering & Functional Programming course, upgrade to CoddyKit PRO. The Scala for Backend Engineering & Functional Programming course includes 4 lessons in total.

What will I learn in “Composing Domains”?

Build larger models. You practise Scala for Backend Engineering & Functional Programming with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Scala for Backend Engineering & Functional Programming?

No prior experience is required. Scala for Backend Engineering & Functional Programming on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 4 of 4.

How long does the “Composing Domains” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Scala for Backend Engineering & Functional Programming lesson?

Yes. Every Scala for Backend Engineering & Functional Programming lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Modeling with ADTs
  2. Smart Constructors
  3. Newtypes
  4. Composing Domains
← Back to Scala for Backend Engineering & Functional Programming