Course outline

Kotlin / Concurrency, interoperability, and integrated design / Lesson 18

Build a validated reservation pipeline and review its limits

Combine parsing, value models, sealed outcomes, collection transformation, and explicit execution boundaries in a small offline design.

About 4 min readContent reviewed
On this page

Lesson goals

  • Trace invalid input and successful data through distinct typed stages.
  • Distinguish an offline policy demonstration from durable concurrent booking.

Keep the capstone promise intentionally small

The capstone receives fixed textual seat requests and produces labels for valid positive counts or invalid input. It does not contact a workshop service, allocate actual capacity, store reservations, or deliver notifications. This small promise allows type and control-flow decisions to be inspected without hiding unimplemented reliability claims behind realistic names. A real reservation application would add authenticated resource ownership, atomic allocation, request identity, persistence, recovery, and external integration. Keep these missing responsibilities visible so a successful local calculation is never mistaken for a complete production workflow.

Use outcomes that retain the reason for rejection

The parser below distinguishes malformed integer text from a nonpositive parsed value. A sealed result gives valid input a required integer and invalid input a required reason. These are synthetic stable teaching codes; a public API would need a separately designed wire contract. The classifier trims whitespace deliberately, and its test inputs document that policy. Do not normalize identifiers or culturally meaningful text indiscriminately just because trim is convenient for numeric input. The expected labels are reasoned from this local policy, not observed from a compiler run.

kotlin A complete offline typed parsing pipeline
sealed interface SeatInput
data class ValidSeats(val count: Int) : SeatInput
data class InvalidSeats(val reason: String) : SeatInput
fun parseSeats(text: String): SeatInput {
    val count = text.trim().toIntOrNull()
        ?: return InvalidSeats("not-an-int")
    return if (count > 0) ValidSeats(count) else InvalidSeats("not-positive")
}
fun label(input: SeatInput): String = when (input) {
    is ValidSeats -> "valid:${input.count}"
    is InvalidSeats -> "invalid:${input.reason}"
}
fun main() {
    listOf(" 2 ", "bad", "0").map(::parseSeats).map(::label).forEach(::println)
}
Expected output
valid:2
invalid:not-an-int
invalid:not-positive

Expected lines are valid:2, invalid:not-an-int, invalid:not-positive. The source requires a compatible Kotlin toolchain supporting sealed interfaces and was not compiled.

Separate pure policy from effects and lifecycle

parseSeats and label use only their arguments and return values, making their behavior independent from the real clock, network, or mutable singleton. main owns the fixed input and presentation effect. If the next stage performs actual reservations, introduce an explicit application boundary rather than adding database or coroutine launches inside the parser. A suspend modifier would allow calling suspending work but would not establish an owner, dispatcher, or transaction. The architecture should preserve the simple parsing contract while adding the separate operational responsibilities where they can be tested honestly.

Aggregate only after invalid data has an explicit path

The second example uses mapNotNull to retain only valid positive integer values and reports the accepted total. This is a different product policy from retaining rejection reasons: invalid items are intentionally skipped. That may be appropriate for a preview summary, but it would be misleading for a workflow that must explain every rejected booking. Name the skipped count or preserve result records when completeness matters. The example uses small values that fit Int and does not claim overflow safety for arbitrary large inputs.

kotlin An explicit skip-and-summarize policy
fun positiveCount(text: String): Int? =
    text.toIntOrNull()?.takeIf { it > 0 }
fun main() {
    val source = listOf("2", "bad", "0", "3")
    val accepted = source.mapNotNull(::positiveCount)
    println("accepted=${accepted.size}")
    println("skipped=${source.size - accepted.size}")
    println("seat_total=${accepted.sum()}")
}
Expected output
accepted=2
skipped=2
seat_total=5

Expected output is accepted=2, skipped=2, seat_total=5. Skipping is visible in the count. This complete source is illustrative and was not executed.

Review correctness and evidence before adding features

Check malformed syntax, integer range overflow, zero, negatives, whitespace policy, and ordinary values. Review whether error codes and display text are separated and whether every allowed outcome is handled. Inspect shallow references if richer mutable records are introduced. For coroutine extensions, verify cancellation, scope ownership, and state protection in the actual configured toolchain. This course’s authoring checks validate structure and bilingual technical parity only; Kotlin programs and coroutine behavior were not executed. A truthful capstone report states those limits and lists the next isolated checks instead of manufacturing test success or deployment readiness.

Common errors and fixes

Skipped invalid requests are reported as all processed successfully.

Why it happens
A filtering policy erased required rejection evidence.
How to fix it
Keep per-input outcomes or report skipped items according to the product contract.
Prevent it next time
Define completeness before choosing mapNotNull.

The parser starts unowned background reservations.

Why it happens
Pure input interpretation was mixed with operational effects.
How to fix it
Move effects to an explicit application scope and contract.
Prevent it next time
Keep parsing independently checkable with fixed data.

A local output example is presented as production booking proof.

Why it happens
The demonstration’s missing responsibilities were ignored.
How to fix it
State the offline scope and require separate persistence and concurrency evidence.
Prevent it next time
Report only tests actually executed in the declared environment.

Check your understanding

1. Why is mapNotNull not a drop-in replacement for the sealed parser?

Show worked solution

It can intentionally discard invalid inputs and their detailed causes. The sealed parser preserves one outcome per input and distinguishes syntax from domain rejection. Choose based on whether the application needs complete feedback or a filtered preview, and make skipped data visible whenever losing it would mislead the user.

2. List three next checks before adapting the capstone to a real service.

Show worked solution

Compile the source with the project’s declared compatible toolchain and verify boundary cases; test the actual authoritative allocation transaction under controlled concurrency; and verify request identity, authorization, and cancellation/recovery behavior at their own boundaries. The last group may require several checks. None can be inferred from printing valid:2 in an offline example.

Key takeaways

  • Typed stages clarify meaning when each boundary retains its own responsibility.
  • A useful final report separates reasoned examples, executed checks, and missing operational evidence.

Official references