The previous lesson fixed the overall scope of BiblioTech's final project and its chosen architecture. Before touching a single line of code, this lesson turns that scope into something much more concrete: a precise list of functional and non-functional requirements, and a plan of small iterations that avoids the temptation to try to build everything at once. Planning before coding isn't unnecessary bureaucracy: it's what lets you know, in Lesson 3, exactly what to build first and why, instead of moving forward blindly, assembling pieces in an arbitrary order.
Contents
- Functional requirements: concrete use cases
- Non-functional requirements
- Phased planning: four iterations
- Basic task estimation per iteration
- Final choice of technologies
- Functional requirements: concrete use cases
A functional requirement describes something the system must do: a concrete action a user (or an API client) can request and expect a result from. For BiblioTech, the final project's list of functional requirements is as follows:
| # | Use case | Input | Expected result |
|---|---|---|---|
| FR1 | Add a book or magazine | Title, author, ISBN (or other Book/Magazine data, Module 3) |
The item is added to Catalog |
| FR2 | Remove an item from the catalog | Item identifier | The item no longer appears in the catalog |
| FR3 | Search for items by title or author | Search text | List of matching items (LINQ, Module 4) |
| FR4 | Add a member | Member's name | The member is added to Members, with a unique Id |
| FR5 | Register a loan | Item ISBN, member Id |
A Loan is created; the item is marked as unavailable |
| FR6 | Register a return | Loan identifier | ReturnDate is assigned; a penalty is calculated if it's late (Module 8, IPenaltyPolicy) |
| FR7 | Look up a book's external metadata | ISBN | Additional data from the external service (Module 5), or a clear absence if it's unavailable |
Each of these seven requirements maps almost directly onto an existing method in Library, or a
small combination of them — the table itself is, in effect, a map of which part of the code already
built covers each requirement.
- Non-functional requirements
A non-functional requirement doesn't describe an action, but a quality the system must have while doing what it does. These are harder to verify with a simple use case, but just as important:
- Reasonable performance: query operations (FR3, listing the catalog) must respond in an acceptable time even with a moderately sized catalog (hundreds or a few thousand items); no extreme performance goal is being pursued here, only avoiding obviously inefficient operations (for example, loading the entire catalog into memory on every request when a database query would be enough).
- Basic security: the API must not needlessly expose sensitive data (for example, connection
strings or internal database details in error messages returned to the client); production
connection strings must live outside the source code (Lesson 5, per-environment configuration),
never written directly in
Program.cs. - Maintainability: thanks to Module 8's architecture (
ILibraryRepository+ dependency injection + unit tests), changing the persistence mechanism, adding a new endpoint, or fixing a bug should be possible without rewriting parts of the system unrelated to the change. - Resilience against external failures: the external metadata lookup (FR7) depends on a
third-party service that may fail or not respond; Module 5 already showed this case is caught
with
try/catcharoundHttpRequestException, returningnullinstead of propagating the failure to whoever requested the metadata.
Unlike functional requirements, these aren't "marked as done" with a single one-off test: they're verified continuously throughout the whole project, and reviewed explicitly in Lesson 4's quality checklist.
- Phased planning: four iterations
Trying to build all seven capabilities from section 1 at once, with no order to it, is the most direct recipe for getting stuck halfway with nothing working end to end. Instead, the project is organized into four small iterations, each delivering something that works on its own before moving to the next:
flowchart LR
I1["Iteration 1<br/>Domain + Persistence<br/>(already built, Modules 3-5-8)"] --> I2["Iteration 2<br/>API<br/>(Lesson 3)"]
I2 --> I3["Iteration 3<br/>Client / UI<br/>(Lesson 3)"]
I3 --> I4["Iteration 4<br/>Polish<br/>(Lessons 4-5)"]
| Iteration | What it delivers | Requirements it covers |
|---|---|---|
| 1. Domain + Persistence | Confirming that Library, the domain model, and ILibraryRepository with its chosen implementation (section 5) compile and work in isolation, with no API yet |
Foundation of FR1-FR7 (already built in earlier modules, just verified to still be in order) |
| 2. API | ASP.NET Core Minimal API HTTP endpoints over the domain, with DI already registered | FR1-FR7 exposed over HTTP |
| 3. Client / UI | A simple client (Blazor, or a console app with HttpClient) consuming the Iteration 2 API |
End-to-end verification of FR1-FR7 from outside the API process |
| 4. Polish | Integration tests, logging, quality checklist, deployment | Non-functional requirements from section 2 |
This split into iterations isn't arbitrary: each one depends exclusively on the previous one, and
each one is, by itself, verifiable — an API that responds correctly to curl, even with no client
yet, is already a tangible result of Iteration 2, not half-finished work with no value of its own.
- Basic task estimation per iteration
An estimate doesn't need to be precise to the minute to be useful; a relative sense of effort is enough to help prioritize and to spot ahead of time which iteration is most likely to get stuck:
| Iteration | Main tasks | Relative effort |
|---|---|---|
| 1. Domain + Persistence | Verify the four ILibraryRepository implementations (Module 8) still compile; choose one for production |
Low (already built, just verification) |
| 2. API | Solution structure, DI registration, 5-7 endpoints (one per functional requirement) | Medium-high (the iteration with the most new code) |
| 3. Client / UI | One component or screen per main use case (catalog, loan, return) | Medium |
| 4. Polish | Integration tests, logging, checklist, Dockerfile, per-environment configuration |
Medium |
The iteration with the highest relative effort (the API) is precisely the one that leans most heavily on work already done in Module 8: most of that "load" is assembling and registering services, not new logic invented from scratch.
- Final choice of technologies
With the requirements now clear, it's time to fix, unambiguously, which specific technology will be used at each layer — summarizing decisions already presented or compared in earlier modules:
| Layer | Chosen technology | Module where it was covered |
|---|---|---|
| Domain | Existing C# classes (LibraryItem, Book, Magazine, Member, Loan) |
Module 3 |
| Persistence abstraction | ILibraryRepository |
Module 8 (Lesson 3) |
| Concrete production persistence | Entity Framework Core over SQLite (EntityFrameworkRepository + LibraryDbContext) |
Module 5 |
| Dependency injection | ASP.NET Core's services container (AddScoped/AddSingleton/AddTransient) |
Module 8 (Lesson 3) |
| API | ASP.NET Core Minimal APIs | Module 7 |
| Client | Blazor (or a simple HttpClient consumer) |
Module 7, Module 5 |
| Testing | xUnit + Moq | Module 8 (Lesson 4) |
| External metadata | HttpClient against a simulated external service |
Module 5 |
Choosing Entity Framework Core (instead of text, JSON, or SQLite with raw ADO.NET) as production
persistence responds to a concrete reason: it's the only one of Module 5's four options designed
to scale comfortably as the data model grows, and it integrates naturally with ASP.NET Core's DI
container via AddDbContext (already seen in 08-03, section 7). Nothing prevents replacing it
with any of the other three at any point, however: that's precisely the guarantee
ILibraryRepository offers.
Common Mistakes and Tips
- Confusing functional requirements with implementation tasks: "use Entity Framework Core" is not a functional requirement (it doesn't describe what the system does for the user); it's a technical decision from section 5. Keeping the two separate avoids confusing requirement lists.
- Skipping non-functional requirements because they seem "less concrete": they're just as real as functional ones, only verified differently (continuous review, a checklist) instead of with a one-off use case.
- Planning iterations that depend on each other circularly: if Iteration 3 (Client) needed changes to Iteration 1 (Domain) to work, the plan has an ordering problem; check that each iteration only depends on earlier ones, never on later ones.
- Tip: if an iteration looks "too big" when estimating it, that's a good sign it can be split into two smaller iterations, each with its own verifiable result.
Exercises
-
For each of the seven functional requirements (FR1-FR7) in section 1's table, indicate which iteration from section 3 mainly covers it and which HTTP endpoint (verb + route) you'd expect to implement it in Iteration 2.
-
A teammate proposes adding, halfway through Iteration 2, a new requirement: "allow reserving a book that isn't available, so it's held once it's returned." Explain why, according to this lesson's planning criteria, that requirement should be noted down for a future iteration instead of being added immediately to the current Iteration 2.
Solutions
| Requirement | Iteration | Approximate endpoint |
|---|---|---|
| FR1 (add item) | 2 | POST /books |
| FR2 (remove item) | 2 | DELETE /books/{isbn} |
| FR3 (search) | 2 | GET /books?search=... |
| FR4 (add member) | 2 | POST /members |
| FR5 (loan) | 2 | POST /loans |
| FR6 (return) | 2 | POST /loans/{id}/return |
| FR7 (external metadata) | 2 | GET /books/{isbn}/metadata |
Adding a new requirement halfway through an ongoing iteration breaks the central principle of phased planning: each iteration must deliver a closed, verifiable scope before moving to the next. Accepting the change right away would unpredictably lengthen Iteration 2 and delay the rest of the plan; the right move is to note the idea down (for example, as "Iteration 5: reservations," outside this final project's already-closed scope) and continue with what's already planned, just as would be done with any new functionality proposed during development, as already warned in the previous lesson.
Conclusion
This lesson has turned Lesson 1's overall scope into something much more concrete: seven
functional requirements traceable to existing code, a set of non-functional requirements that are
watched continuously, a four-iteration plan with clear dependencies between them, a relative
effort estimate, and the final choice of technology for each layer. With this plan now closed, the
next lesson, Implementation, stops planning and starts assembling: it will build the
multi-project solution structure, register ILibraryRepository and Library in ASP.NET Core's DI
container, and expose the endpoints corresponding to each of this lesson's seven requirements.
C# Programming Course
Module 1: Introduction to C#
- Introduction to C#
- Setting Up the Development Environment
- Hello World Program
- Basic Syntax and Structure
- Variables and Data Types
- Arrays and Strings
Module 2: Control Structures
Module 3: Object-Oriented Programming
- Classes and Objects
- Methods
- Constructors and Destructors
- Inheritance
- Polymorphism
- Encapsulation
- Abstraction
- Structs and Records: Value Types and Reference Types
Module 4: Advanced C# Concepts
- Interfaces
- Delegates and Events
- Pattern Matching and Modern C# Features
- Generics
- Collections
- LINQ (Language Integrated Query)
- Asynchronous Programming
Module 5: Working with Data
- File I/O
- Serialization
- Database Connectivity
- Entity Framework
- Working with JSON and Consuming REST APIs
Module 6: Advanced Topics
- Reflection
- Attributes
- Dynamic Programming
- Memory Management and Garbage Collection
- Multithreading and Parallel Programming
Module 7: Building Applications
Module 8: Best Practices and Design Patterns
- Coding Standards and Best Practices
- Design Patterns
- Dependency Injection and Inversion of Control
- Unit Testing
- Code Review and Refactoring
