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.
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.
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)
}
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.
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()}")
}
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.