RizTech Academy logo
RizTech Academy
Capstone: Tiffin Service TrackerLesson 1 of 525 min

Planning the application before writing it

Ten modules of pieces. This one assembles them into a single application you could hand to somebody: a command-line tracker for a Pune tiffin service.

Before writing a line of it, we are going to plan — because the most valuable half hour on any project is the one spent deciding what it does before deciding how.

The problem

A tiffin service delivers home-cooked lunches to subscribers across Wagholi, Kharadi and Bavdhan. Today the owner keeps it in a notebook: who took how many tiffins on which day. At month end she works out each bill by hand, and occasionally gets one wrong.

She wants a program that records deliveries and produces the month's bills.

What it will do

Three commands. That is the whole scope.

tiffin-tracker add <yyyy-mm-dd> <customer> <tiffins>   record a delivery
tiffin-tracker list                                    show every delivery
tiffin-tracker report [yyyy-mm]                        bill for a month

Writing the interface first is the cheapest design review there is. Three commands with those arguments already answer: data is per-delivery not per-month, a delivery has a whole number of tiffins, and the report is monthly. Each of those could have gone another way, and now none of them will be discovered halfway through.

What it will not do

Being explicit about this is the part people skip.

  • No database. A CSV file. The whole dataset is a few thousand rows a year, and a file is readable, editable and backed up by copying.
  • No web interface, no API. A command line.
  • No authentication, no users. One person runs it on one laptop.
  • No payments. It produces a bill; money is somebody else's business.
  • No editing or deleting a delivery. Add and report. Corrections are made by editing the CSV, which is a defensible choice for a file you can open in a spreadsheet, and a terrible one for anything else.

Every one of those is a decision, not an omission. A scope you can state is a scope you can finish, and the point of a capstone is to finish.

The model

Four types, and choosing between record, enum and class is module 3's whole lesson applied:

Thing Type Why
Plan enum A fixed set with a price each. A typo must not compile.
Subscriber record Name, pincode, plan, start date. Value, not identity.
Delivery record Date, customer, tiffin count. Immutable once it happened.
Parsed<T> generic record The rows that loaded and the problems that did not

Parsed<T> is the one worth noticing. Loading a file produces two things — the good rows and the bad ones — and returning both together is what lets the program report every problem instead of stopping at the first. That is module 4's Result<T> in the shape this application needs.

The classes

Class One sentence
Money Formats and parses paise
DeliveryStore Reads and writes the delivery CSV
BillingService Totals, groups, and renders the report text
Main Parses arguments, prints, sets exit codes

Each passes module 10's test — one sentence, no "and" that hides a second job.

And the split that makes it testable: BillingService returns the report as a String; Main prints it. Deciding and doing, separated.

The decisions worth writing down

Money is long paise. Never a double. Module 2's lesson, and the reason the Money class exists at all — so the decision is made once, in one file.

Dates are LocalDate. A delivery is a calendar event, not a moment, so no zone and no time. A YearMonth identifies a billing period.

The CSV is the source of truth, written atomically — temp file, then move — so a crash cannot truncate a month of records.

Bad rows are reported, not fatal. A file with two broken lines still produces a report for the rest, and names both problems with their line numbers. This is the difference between a script and a tool, and it appears in the exit code: finishing with data problems is not the same as failing.

Exit codes are distinct:

Code Means
0 Success
1 Usage error — unknown or missing command
2 Bad input — an argument that failed validation
3 I/O failure — the data file could not be read or written
4 Completed, but some rows in the file were unreadable

What it will look like

Writing the expected output now means the formatting decisions are made before any code depends on them:

Tiffin bill - 2026-09
=====================
Customer               Tiffins        Amount
Arjun                        1      Rs 91.00
Kavita                       3     Rs 222.36
Priya                        2     Rs 164.70
Total                              Rs 478.06
Busiest day: 2026-09-01

Customers sorted by name, amounts right-aligned, a total, and one extra fact the owner asked for.

Which module each piece comes from

Worth seeing laid out, because the capstone is the argument for the earlier lessons:

Piece Module
long paise, integer division for formatting 2
String.formatted, text blocks, split(",", -1) 2
Records, enums, compact-constructor validation 3
Parsed<T> 4
TreeMap for a sorted report, groupingBy 5, 6
Optional for the busiest day 6
Messages naming the line and the value, wrapping with a cause 7
Files, atomic move, LocalDate, YearMonth 8
Maven, JUnit, @TempDir, exit codes 10

Module 9 is the one that does not appear, and that is correct: a single-user command-line tool has no concurrency, and adding threads to it would be the mistake that lesson warned against.

How to work through it

Four lessons follow, each building one layer and each ending with something that runs:

  1. The domain — Plan, Subscriber, Delivery, Money, and the tests that make an invalid one impossible.
  2. Storage — DeliveryStore, Parsed<T>, atomic writes, and collecting bad rows.
  3. The command line — Main, argument parsing, exit codes, failing usefully.
  4. Tests and packaging — the full suite, and a jar you can hand over.

Type it. Do not copy it. The errors you make typing it are the ones this course has spent ten modules preparing you to read.

Check your work

Why write the command-line interface before the code? It forces the important decisions — what a record is, what the reporting period is — into the open, where they cost minutes rather than a rewrite.

Why is "no database" a decision rather than a shortcut? The dataset is small, a CSV is readable and editable by the owner, and a file can be backed up by copying. A decision has a reason; an omission does not.

Why does Parsed<T> carry two lists? So loading can report every bad row instead of stopping at the first, and so the caller decides what to do about them.

Why does BillingService return a String? So the report can be tested. Printing is Main's job.

Why is money a long of paise? A double accumulates representation error and produces bills that display correctly and fail reconciliation — module 2's lesson.

Why five exit codes? So a script or CI job can distinguish failure from "finished, but some rows were bad". One code for everything cannot express that.

Which module does not appear in the capstone, and why is that right? Concurrency. A single-user command-line tool has no need for it, and adding threads would be exactly the mistake module 9 warns about.

Practice 2, the scope list. A good "will not do" list has a reason beside each entry. If you wrote "no web interface" with no reason, ask what you would say to the owner when she asks for one — "the command line is enough for one person on one laptop, and a web version is a week of work" is an answer; silence is not.

Practice 4, the format. Writing the expected output first fixes the column widths, the sort order and the currency format before any code depends on them. The format string %-20s%10d%14s%n follows directly from that sketch, and getting it from the sketch rather than by trial and error is the point.

Practice

  1. Write the three commands out with their arguments, as a help message, before reading the next lesson.

  2. Write the "will not do" list with a reason beside each entry. Five items minimum.

  3. Choose the types. For each of plan, subscriber, delivery and the result of loading a file, decide record, enum, class or generic record, and write one clause of justification.

  4. Sketch the report. Column headings, widths, alignment, sort order, and the total line. Then write the printf format string that would produce it.

  5. Decide the exit codes. Write the table before you write the code. Then name a real situation for each one.

  6. Harder — design one thing differently. Pick one decision above — the CSV, the three commands, reporting rather than failing on bad rows — and write a paragraph arguing for the opposite. Then say which you would actually build and why. A design you cannot argue against is a design you have not examined.

Next: the domain, and making an invalid delivery impossible to create.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship