RSS Amplifier

Crafting software · Aug 11, 2026

Kata 2: Library loan

0
Sign in to vote or save

Emmanuel Valverde Ramos · Crafting software

Practise designing operations as contracts: what must be true before an operation, what happens when the operation is accepted, and what must remain true when the operation is rejected.

This kata is not about records, tables, CRUD screens, or status fields. It is about behaviour, conditions, aftereffects, and safe collaboration.

A small library lends books to members.

Members can borrow available books up to a fixed limit. A book already borrowed by one member cannot be borrowed by another. A borrowed book may be reserved by another member. When a reserved book is returned, the waiting member gets priority.

Build the behaviour needed to borrow, return, and reserve books safely.

A member should be able to:

  • Borrow an available book

  • See their borrowed books

  • Return a book they borrowed

  • Reserve a book borrowed by another member

  • Borrow a returned book that was reserved for them

The library must reject invalid operations and preserve the previous valid state.

Use this vocabulary to describe the business problem. It is not a proposed implementation model.

  • Library

  • Book

  • Member

  • Borrowed book

  • Available book

  • Waiting member

  • Reservation

  • Loan limit

  • Borrowing request

  • Return request

  • Reservation request

  • Rejected request

  • A member may borrow at most 3 books.

  • A book can be borrowed by one member at a time.

  • A member cannot reserve a book they already borrowed.

  • An available book cannot be reserved.

  • A returned reserved book is available only to the waiting member.

  • A rejected operation must not change the previous valid state.

This diagram describes the business lifecycle of a book in this kata. It is not an implementation model.

Before moving to the next scenario:

  • Is the behaviour covered by a failing test first?

  • Is the expected outcome observable from outside the implementation?

  • Are previous scenarios still passing?

  • Did a rejected operation preserve the previous state?

  • Does the test describe behaviour rather than internal data?

  • Is there duplication or awkward naming worth refactoring now?

⚠️ This kata has counterexample that you do not have to follow, they are meant to force you, to think about the sad path

A member who has not borrowed anything should have no borrowed books.

This is the smallest observable behaviour in the kata. It gives the system a clear starting point before any borrowing, returning, or reservation rules appear.

Before the library can track changes, it must be clear what the initial state looks like.

A new member should not appear to have borrowed books by default. If the system cannot represent “nothing has happened yet”, every later change becomes harder to trust.

Alice is a library member. She has not borrowed any books.

When the library checks Alice’s borrowed books, none are shown.

Alice has not borrowed anything, but the library shows one or more borrowed books.

That would be wrong because the system would be reporting loans that never happened.

This rule anchors the rest of the kata.

Every later scenario either changes this observable state or protects it from invalid operations.

Alice is a member, but she has not borrowed anything yet.

Scenario: Show no borrowed books for a new member
  Given Alice has not borrowed any books
  When the library checks Alice's borrowed books
  Then no borrowed books are shown

A member can borrow a book when that book is available.

After the borrowing succeeds, the book is no longer generally available, and the library can show that the member borrowed it.

This is the first operation with an aftereffect: the world changes after the operation.

Borrowing is the central behaviour of the library.

If the system cannot express what changes when a book is borrowed, later rules such as loan limits, returns, and reservations have no foundation.

The important part is not only that the operation succeeds. The important part is that the result of the operation can be observed afterwards.

“Domain-Driven Design” is available.

Alice borrows it.

The book is now borrowed by Alice and appears among Alice’s borrowed books.

“Domain-Driven Design” is available.

Alice borrows it.

The system still reports the book as generally available.

That would be wrong because another member could now borrow the same book.

This rule introduces the first accepted state transition.

The kata stops being only a query and becomes behaviour that changes what the library knows.

Alice borrows a book that nobody else has borrowed.

Scenario: Borrow one available book
  Given "Domain-Driven Design" is available
  When Alice borrows "Domain-Driven Design"
  Then "Domain-Driven Design" is borrowed by Alice

Once Alice borrows a book, it cannot be treated as generally available.

Scenario: Make a borrowed book unavailable
  Given "Domain-Driven Design" is available
  When Alice borrows "Domain-Driven Design"
  Then "Domain-Driven Design" is not available

The library can show which book Alice borrowed.

Scenario: Show one borrowed book for a member
  Given Alice borrowed "Domain-Driven Design"
  When the library checks Alice's borrowed books
  Then "Domain-Driven Design" is shown for Alice

A member may borrow more than one book.

Scenario: Show several borrowed books for a member
  Given Alice borrowed 3 books
  When the library checks Alice's borrowed books
  Then the library shows 3 borrowed books for Alice

A member can borrow up to 3 books.

If they already have 3 borrowed books, a new borrowing request must be rejected.

The important part is not only rejection. The requested book must remain available because the borrowing did not happen.

The library has a policy that controls how many books a member can hold at the same time.

The system must protect that policy while keeping the rest of the library state consistent.

This is the first rule where borrowing depends not only on the book, but also on the member’s current situation.

Alice has already borrowed 2 books.

She borrows another available book.

Alice now has 3 borrowed books.

Alice has already borrowed 3 books.

She tries to borrow “Refactoring”.

The request is rejected and “Refactoring” remains available.

The kata now has a business policy.

Borrowing is no longer valid just because the book is available. The member’s situation also matters.

Alice already borrowed 2 books and borrows one more.

Scenario: Borrow up to the loan limit
  Given Alice already borrowed 2 books
  When Alice borrows another available book
  Then the library shows 3 borrowed books for Alice

Alice already borrowed 3 books and cannot borrow a fourth.

Scenario: Reject borrowing above the loan limit
  Given Alice already borrowed 3 books
  When Alice tries to borrow another book
  Then the borrowing request is rejected

A failed borrowing request must not make the requested book unavailable.

Scenario: Keep the book available after exceeding the loan limit
  Given Alice already borrowed 3 books
  When Alice tries to borrow "Refactoring"
  Then "Refactoring" remains available

A book borrowed by one member cannot be borrowed by another member.

The system must reject the second borrowing request and preserve the current borrower.

This rule protects the basic meaning of borrowing.

If the system allowed another member to borrow the same book, the library would no longer know who actually has it.

This rule also makes rejection more concrete. Rejecting an operation should not silently alter the previous valid state.

Alice borrowed “Refactoring”.

Bob tries to borrow “Refactoring”.

The request is rejected.

Alice borrowed “Refactoring”.

Bob tries to borrow it, and the system changes the borrower from Alice to Bob.

That would be wrong because Bob’s request was invalid.

The kata now checks that rejected operations preserve the previous valid state.

Rejection is not just an error result. It is also a promise that the existing library situation has not been damaged.

Alice has already borrowed a book. Bob cannot borrow the same book.

Scenario: Reject borrowing a book borrowed by another member
  Given Alice borrowed "Refactoring"
  When Bob tries to borrow "Refactoring"
  Then the borrowing request is rejected

Bob’s failed borrowing request must not replace Alice as the borrower.

Scenario: Keep the current borrower after a rejected borrow
  Given Alice borrowed "Refactoring"
  When Bob tries to borrow "Refactoring"
  Then "Refactoring" is still borrowed by Alice

A borrowed book can be returned by the member who borrowed it.

After a valid return, the book can be borrowed again, and it no longer appears among the member’s borrowed books.

Invalid returns must be rejected.

Returning is not just the opposite of borrowing. It has its own conditions.

The book must actually be borrowed, and the person returning it must be the borrower.

This rule also makes the lifecycle of a borrowed book more complete: it can move from available, to borrowed, back to borrowable again.

Alice borrowed “Refactoring”.

Alice returns it.

“Refactoring” can be borrowed again, and Alice no longer has it among her borrowed books.

Alice borrowed “Refactoring”.

Bob tries to return it.

The request is rejected because Bob is not the borrower.

The kata now has a second operation that changes the same business reality.

Borrowing and returning must agree about what it means for a book to be borrowed, available, and associated with a member.

Alice returns a book she borrowed.

Scenario: Return one borrowed book
  Given Alice borrowed "Refactoring"
  When Alice returns "Refactoring"
  Then "Refactoring" can be borrowed again

After returning a book, Alice should no longer see it among her borrowed books.

Scenario: Remove a returned book from the member's borrowed books
  Given Alice borrowed "Refactoring"
  When Alice returns "Refactoring"
  Then the library shows no borrowed books for Alice

Alice cannot return a book that nobody borrowed.

Scenario: Reject returning a book that is not borrowed
  Given "Refactoring" is available
  When Alice tries to return "Refactoring"
  Then the return request is rejected

Bob cannot return a book borrowed by Alice.

Scenario: Reject returning a book borrowed by another member
  Given Alice borrowed "Refactoring"
  When Bob tries to return "Refactoring"
  Then the return request is rejected

A member can reserve a book only when another member has borrowed it.

Reserving means the member is waiting for that book.

A member cannot reserve an available book because they can borrow it directly. A member also cannot reserve a book they already borrowed.

Reservation introduces future intent.

The book is still borrowed now, but the library must remember that someone else is waiting for it.

This changes the meaning of a later return.

Alice borrowed “Refactoring”.

Bob reserves it.

Bob is now waiting for “Refactoring”.

“Refactoring” is available.

Bob tries to reserve it.

The request is rejected because available books should be borrowed, not reserved.

The kata now has behaviour that affects a future operation.

Reserving a book does not make it available immediately, but it changes what should happen when the borrower returns it.

Alice has borrowed a book. Bob wants to wait for it.

Scenario: Reserve a borrowed book
  Given Alice borrowed "Refactoring"
  When Bob reserves "Refactoring"
  Then Bob is waiting for "Refactoring"

Bob should borrow an available book, not reserve it.

Scenario: Reject reserving an available book
  Given "Refactoring" is available
  When Bob tries to reserve "Refactoring"
  Then the reservation request is rejected

Alice cannot reserve a book she already borrowed.

Scenario: Reject reserving a book borrowed by the same member
  Given Alice borrowed "Refactoring"
  When Alice tries to reserve "Refactoring"
  Then the reservation request is rejected

When a reserved book is returned, it does not become generally available to everyone.

It becomes available to the waiting member.

That waiting member has priority. Other members cannot take the book first.

This is the key rule in the kata. It changes the meaning of “return”.

Before reservations, returning a book simply made it available. After reservations, returning a book may make it available only to a specific member.

Alice borrowed “Refactoring”.

Bob is waiting for it.

Alice returns it.

“Refactoring” is now available to Bob.

Alice returns “Refactoring”, which Bob was waiting for.

Carol tries to borrow it before Bob.

The request is rejected, and the book remains available to Bob.

The kata now forces the reader to think about aftereffects more carefully.

The same operation, returning a book, can have different outcomes depending on the business situation.

Alice returns a book Bob was waiting for.

Scenario: Return a book with one waiting member
  Given Alice borrowed "Refactoring"
  And Bob is waiting for "Refactoring"
  When Alice returns "Refactoring"
  Then "Refactoring" is available to Bob

Bob can borrow the book that was returned for him.

Scenario: Allow the waiting member to borrow the returned book
  Given "Refactoring" is available to Bob
  When Bob borrows "Refactoring"
  Then "Refactoring" is borrowed by Bob

Carol cannot borrow a returned book that is waiting for Bob.

Scenario: Reject borrowing a returned book reserved for another member
  Given "Refactoring" is available to Bob
  When Carol tries to borrow "Refactoring"
  Then the borrowing request is rejected

Carol’s failed borrowing request must not remove Bob’s priority.

Scenario: Keep the reservation after a rejected borrow by another member
  Given "Refactoring" is available to Bob
  When Carol tries to borrow "Refactoring"
  Then "Refactoring" remains available to Bob

No posts

Read the original on emmanuelvalverderamos.substack.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.