An Introduction to Software Registration for Web Developers

Development is a dance between code and data; we want the flexibility of data descriptions, but the certainty of coded solutions. Relationships between objects are rich, but dependencies cause issues.
Registration is a common pattern that allows developers to add a component to a formal list at runtime, allowing erstwhile data to participate in code.
So a register is just a list of objects that the app treats as canonical, with a few caveats. We almost certainly don’t want replication, which implies we need some sort of primary key. It also implies there is some sort of raw list of data that seeds this register. Like many patterns, the advantage to using registration is that it is a good way to separate concerns. This post shows how simple but relatively useful this pattern is. You can follow the C# code, or clone it from GitHub.

Via Paul Downey, UK Government Digital Service
The concept of a hardware register slightly overshadows the simpler concept of software registration. But we do see registration quite commonly when using APIs — for example, you often have to register listeners to events when programming buttons in UI.
A more formal description of a register isn’t necessary for this post, but for completeness have a look at what Paul Downey wrote here, and also Ade Adewunmi for the renowned UK Government Digital Service. A few useful tenets are repeated: each registered object has a unique identifier; data replication is to be avoided; allow registers to talk to other registers; and treat registers as trusted data.
I hope many readers have had the chance to go on holiday this summer, and the simple example I give of registration is just a summer-themed matching of a party of holidaymakers to the right-sized cabin. So we have two registers: cabins and holiday parties.
Let’s take a look at the details:
- A “cabin” has a door number (primary key) and a capacity that represents sleeping space. It also has a date range when it is available.
- A “party” represents a number of holidaymakers under a lead traveller’s name.
- A party can only book (i.e. registration) into a cabin if the cabin is vacant and available.
- As a party can only be registered with a vacant cabin, there is a natural dependency in this model.
- If you unregister a party, their cabin becomes vacant. Consequently, you can’t unregister a cabin if a party is staying in it.
To represent all cabins, we use a JSON list called “allcabins.json”. We iterate over this list, and only register cabins that are available during the date we are interested in.
Similarly, we have a list of prospective parties; we iterate over these holidaymakers, only registering parties when we can put them in a cabin. We should do this before they arrive, to avoid disappointment.
The other thing of interest is that registered objects interact with other registered objects when the party checks for available registered cabins. We would normally prefer to unregister an object as opposed to making changes to a registered object.
Cabins
The cabins that lie in our wild resort are represented in a JSON file, allcabins.json:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
[ { "Number": 6, "Name": "Hill View", "Capacity": 5, "From": "2023/01/01", "To": "2023/12/21" }, { "Number": 3, "Name": "Summerholme", "Capacity": 2, "From": "2023/06/01", "To": "2023/08/31" }, { "Number": 4, "Name": "Dunroamin", "Capacity": 4, "From": "2023/03/01", "To": "2023/10/31" }, ... ] |
Note we will use the cabin number as the primary key. The From/To date format for availability is represented as a string, as it has to be for JSON.
After sucking up the JSON data into its own CabinData struct, we create a Cabin object from it after converting the string dates to C# dates and adding a guestParty variable to note who, if anyone is, staying:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
//Cabin.cs namespace HolidayCabins { public struct CabinData { public short Number { get; set; } public string? Name { get; set; } public short Capacity { get; set; } public string? From { get; set; } public string? To { get; set; } } public class Cabin { private static List<Cabin> registeredCabins = new List<Cabin>(); private Party? guestParty; private CabinData data; private DateOnly from; private DateOnly to; public Cabin(CabinData record) { data = record; if (DateOnly.TryParse(data.From, out DateOnly result)) { from = result; } else Console.WriteLine($"Not a valid date {data.From}"); if (DateOnly.TryParse(data.To, out result)) { to = result; } else Console.WriteLine($"Not a valid date {data.To}"); } ... } } |
The JSON file is handled elsewhere. But the result is that we can make cabins from raw data.
So where do we register them?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
//Cabin.cs public static void RegisterCabin(Cabin cabin, DateOnly date) { if (registeredCabins.Exists(cb => cabin.data.Number == cb.data.Number)) { Console.WriteLine($"{cabin.data.Name} is already registered"); return; } if (cabin.from <= date && date <= cabin.to) { registeredCabins.Add(cabin); Console.WriteLine($"Cabin \"{cabin.data.Name}\" registered"); } else Console.WriteLine($"(Cabin \"{cabin.data.Name}\" not available at the moment)"); } public static void UnregisterCabin(Cabin cabin) { if (cabin.guestParty != null) throw new Exception($"Cannot unregister \"{cabin.data.Name}\" as it is not vacant"); registeredCabins.Remove(cabin); Console.WriteLine($"Cabin \"{cabin.data.Name}\" unregistered."); } |
This is mainly just a gatekeeper to the registeredCabins list. We fulfill two requirements: we don’t allow a cabin with the same number to appear twice, and we check that the cabin is available for the date given. When unregistering a cabin, we check there are no guests already staying.
Holiday Parties
Now for the other register: the guest holiday parties. The JSON data is just the name of the party and its size. So it leads to a simpler object:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
//Party.cs public struct PartyData { public string? PartyName { get; set; } public short Size { get; set; } } public class Party { private static List<Party> registeredParties = new List<Party>(); private Cabin cabin; private PartyData data; public Party(PartyData record) { data = record; } ... } |
While a cabin may be vacant, a Party object must be associated with a cabin once registered — hence we don’t suggest it is nullable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
//Party.cs public static void RegisterParty(Party party) { if (registeredParties.Exists(py => party.data.PartyName == py.data.PartyName)) { Console.WriteLine($"{party.data.PartyName} is already registered"); return; } Cabin cabin = Cabin.FindSuitableCabin(party.data.Size); if (cabin != null) { party.cabin = cabin; cabin.SetGuestParty(party); registeredParties.Add(party); Console.WriteLine($"\"{party.data.PartyName}\" party registered in {cabin}. Happy Holidays!"); } else Console.WriteLine($"No available cabins suitable for the \"{party.data.PartyName}\" party"); } public static void UnregisterParty(Party party) { party.cabin.SetGuestParty(null); registeredParties.Remove(party); Console.WriteLine($"Party \"{party.data.PartyName}\" unregistered."); } |
Again, registration just sits as a gatekeeper, this time making sure that a party has an available cabin of the right size to stay in.
Finally, here are the console responses to our calls from the main program:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
//Program.cs List<CabinData> cabindata = JsonServices.ReadAllCabinsFromFile(); DateOnly todaysdate = DateOnly.FromDateTime(nw); foreach (CabinData cb in cabindata) { Cabin.RegisterCabin(new Cabin(cb), todaysdate); } /* Console Output: Holiday bookings for 9/15/2023 Cabin "Hill View" registered (Cabin "Summerholme" not available at the moment) Cabin "Dunroamin" registered Cabin "Hobbit Hole" registered Cabin "Bear Crescent" not available at the moment) Cabin "Fat Cottage" registered */ List<PartyData> partydata = JsonServices.ReadProspectiveGuestsFromFile(); foreach (PartyData pd in partydata) { Party.RegisterParty(new Party(pd)); } /* Console Output: "Smith" party registered in Hill View. Happy Holidays! "Shah" party registered in Dunroamin. Happy Holidays! "Lebowski" party registered in Hobbit Hole. Happy Holidays! */ //New party appears! Party iverson = new Party("Iverson", 7); Party.RegisterParty(iverson); /* Console Output: "Iverson" party registered in Fat Cottage. Happy Holidays! */ //The Shahs go home Party party = Party.FindRegisteredParty("Shah"); if (party != null) { Party.UnregisterParty(party); } /* Console Output: Party "Shah" unregistered. */ //If the Shahs left, lets close Dunroamin Cabin cabin = Cabin.FindRegisteredCabin(4); if (cabin != null) { Cabin.UnregisterCabin(cabin); } /* Console Output: Cabin "Dunroamin" unregistered. */ //Confirm state of the registerd cabins. Cabin.ReportRegisteredCabins(); /* Console Output: The "Smith" party are staying in "Hill View" The "Lebowski" party are staying in "Hobbit Hole" The "Iverson" party are staying in "Fat Cottage" */ |
The project code is available on GitHub here. If you want to improve the functionality of the code, maybe you can better fit the party size to the capacity of the available cabins.