Hotel mixin sample -- design
Slices
The list in the introduction reveals the use-case slices, modules expressing the logic for each use-case. Each slice is a separate project in the sample code.
Each slice is a separate project in the sample code.
Hotel.Base
is more or less a naked set of properties (with some getters/setters), which form the core domain, without implementing any business logic. This is a recommendation from Ivar Jacobson.Hotel.Reservations
implements the reservation system with a mixin toHotel.Base
Hotel.Accommodation
contains another mixin toHotel.Base
, but it somewhat depends onHotel.Reservation
, because accommodation is a special case of reservationHotel.Discount
is a mixin to the accommodationHotel
mixin (that's right – a "mixinmixin") that extends theHotel.Accommodotion
slice, implemented as a mixin.Hotel.Queue
is a slice extending the reservation slice, implemented as another mixinmixin. If the hotel is booked to capacity for a given week, then guests' reservations are put into a queue
Data is stored in separate repositories, i.e. we use the repository pattern here. Three repositories exist, each with its own type of record(s):
ReservationInfo
– name, room number, weekAccommodationInfo
(andBill
)AccommodationInfo
– name, room numberBill
– number of beers, number of sodas
QueuedReservationInfo
name, weekDiscountInfoRepository
– for counting past visits
As you see, there is one repository for each slice. If you find the implementation strange, read this hand-waving.
we don't store bills
We don't store bills in an archive. This is a concierge system, not an
accounting- or tax system.
Since this is sample code for illustrating a programming method, we don't persist the repositories. What's more, the repositories are mere "cardboard props". In a real application, you'd use a persistence framework or a database.
Domain interdependencies
Some slices are dependent on each other, as can be seen in this diagram:
- Accommodation is a specialization of reservation, i.e. something like a reservation for the current week. For this reason, the accommodation slice builds on the reservation slice, but it does not extend it. The accommodation mixin extends the
Hotel.Base
class, but to this end, theHotel.Base
class must be extended already by the reservation mixin. This dependency is different from extension and explained in further detail in FIXME.
- Queueing of reservations intercepts the reservation mechanism:
- if the
Reserve
method throws aNoMoreRoomsException
, the queueing'sReserve
interceptor puts the reservation requests into the queue - if the
Cancel
method finds that there is a matching reservation in the queue, then the matching reservation replaces the canceled reservation
- if the
- Discount builds on accommodation. It intercepts the accommodation's
CheckOut
method for putting the checked out (accommodated) guest's name into the accommodation history, or, incrementing his count of visits
Overlaps and differences
As it is typical for both mixin-based applications, the slices share method names between themselves. After all, that's the purpose of both approaches to separating concerns. For example, you don't want a single Reserve
method which is a mess of reservation- and queueing logic. You want the Reserve
in Hotel.Reservation
to do reservation work, and the Reserve
in Hotel.Queue
to do queueing work. However, each slice implementation (mixin) sports some extra methods, some of those are just there for testing.
The integration test
The project Hotel.Test
contains the unit-tests for the Mixin Hotel. The file IntegrationTest.cs
demonstrates how the software is intended to be used. (No user interface exists for this sample, just tests.) In this integration test we follow the story of Gonzo, a regular visitor at the Mixin Hotel:
- The test sets
Calendar
to the beginning of time (week 0).Calendar.Init (0);
- The test puts two Gonzo-visits into the accommodation history (you need three visits to be eligible for a discount). (
DiscountMixinMixin.DiscountVisits
is 2.)for (int iter = 0; iter < DiscountMixinMixin.DiscountVisits - 1; iter++) { _hotel.Accommodate ("gonzo"); // accommodation _hotel.CheckOut ("gonzo"); // ditto Calendar.NextWeek(); }
- Next, the test reserves two rooms for week 1 for two different parties. Since the toy hotel has only two rooms, the hotel is filled to capacity in week 1 (note that, at this point in the test, we are still in week 0, so the reservations are in the future).
_hotel.Reserve ("mr and mrs jones", Calendar.Week() + 1); _hotel.Reserve ("mr and mrs smith", Calendar.Week() + 1);
- This makes it difficult for Gonzo to reserve a room for week 1 (consequently, Gonzo's reservation attempt is put into the queue).
var gonzoRoom = _hotel.Reserve ("gonzo", Calendar.Week() + 1);
- Next, one of the reserving parties cancels the reservation. This transforms Gonzo's reservation from a queued reservation to an actual reservation (note: this transformation must be "confirmed", supposedly by an inquiring phone-call by Babara or Manuela; for testing, we inject an "affirmative yes/no-handler" – this will simulate Gonzo saying "Yes" on the phone when asked if he wants the room).
_queueMixin.SetYesNoHandler (new AffirmativeYesNoHandler());
- Now Mr and Mrs Jones cancel their reservation.
_hotel.Cancel ("mr and mrs jones", Calendar.Week() + 1);
- The test makes time pass to week 1, i.e. the week for which rooms have been reserved.
Calendar.NextWeek();
- Gonzo shows up at the front desk, the two concierges transform his reservation into an accommodation, i.e. Gonzo moves into his room.
var gonzoReservation = _hotel.Find ("gonzo", Calendar.Week() + 1);
- Gonzo enjoys his stay, consumes beer and soda, which is added to the
Bill
gonzoAccommodation.Bill.AddConsumption (86, 2);
- By the end of the week, Gonzo shows up at the front desk again for check-out and paying. Since this is Gonzo's third visit, he gets a discount (10 percent).
var gonzoTotalWithDiscount = _hotel.CheckOut ("gonzo");