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
forcomprehensions. - 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
- Modeling with ADTs
- Smart Constructors
- Newtypes
- Composing Domains