Software Design; Architecture should be an option

Ben Watts
4 min readFeb 1, 2022

Software should allow you to choose its architecture at a later date. Now I understand some goals of software are to use a particular piece of architecture for testing purposes, such as a service provided by a 3rd party (Microsoft Azure, Amazon Web Services etc), however code should be as generic as possible so that it can be reused.

For this example we are going to use a very basic application which simply stores helpdesk tickets. The specification requires the following: A user should be able to create, read, update and delete a ticket in the system.

First off, how many of you instantly thought we better get a database designed so we can store these tickets? Or how will our users access this application? The web? A windows app? Electron app? For the moment, let’s create the basics. If we just focus on the requirements, we need a CRUD app. So let’s start on that.

For this example we’re going to create our application in C# (we do need a language for our app). First, let’s create our “HelpDeskTicket” object.

public class HelpDeskTicket
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Message { get; set; }
public string Answer { get; set; }
public string SubmittedBy { get; set; }
public string AnweredBy { get; set; }
public DateTime Created { get; set; }
public DateTime? Closed { get; set; }
}

To allow us to choose our architecture later on, we are going to make a HelpDeskTicketsRepository which will be an interface with the CRUD operations.

HelpDeskTicketsRepository

public interface HelpDeskTicketsRepository
{
void Create(HelpDeskTicket record);
IEnumerable<HelpDeskTicket> GetRecords();
HelpDeskTicket GetRecord(Guid id);
void Update(HelpDeskTicket record);
void Delete(Guid id);
void Save();
}

So now that we have our HelpDeskTicketsRepository, we can go ahead and use it for a concrete class. Let’s create an in memory repository called InMemoryHelpDeskTicketsRepository.

public class InMemoryHelpDeskTicketsRepository : HelpDeskTicketsRepository
{
private Dictionary<Guid, HelpDeskTicket> Records { get; set; }
private Dictionary<Guid, HelpDeskTicket> UpdatedRecords { get; set; }
public InMemoryHelpDeskTicketsRepository()
{
Records = new Dictionary<Guid, HelpDeskTicket>();
UpdatedRecords = new Dictionary<Guid, HelpDeskTicket>();
}
public void Create(HelpDeskTicket record)
{
if (!UpdatedRecords.ContainsKey(record.Id))
UpdatedRecords.Add(record.Id, record);
}
public void Delete(Guid id)
{
if (UpdatedRecords.ContainsKey(id))
UpdatedRecords.Remove(id);
}
public HelpDeskTicket GetRecord(Guid id)
=> Records[id] ?? null;
public IEnumerable<HelpDeskTicket> GetRecords()
=> Records.Select(x => x.Value);
public void Save()
{
Records = UpdatedRecords;
}
public void Update(HelpDeskTicket record)
{
if (UpdatedRecords.ContainsKey(record.Id))
UpdatedRecords[record.Id] = record;
}
}

So, why did we create an InMemoryTicketsRepository? Well, what are the benefits of this? First off this can now be used for any application, console app, web app etc. Of course the issue is the dictionary of tickets will be lost on exit, but this will allow us to use a “real” ticket repository while testing/developing. Basically, we can replace it later because we’re using the HelpDeskTicketsRepository interface.

For example, let’s create a console app which uses the InMemoryTicketsRepository

static void Main(string[] args)
{
var id = Guid.NewGuid();
var helpDeskTicketsRepository = new InMemoryHelpDeskTicketsRepository();
helpDeskTicketsRepository.Create(new HelpDeskTicket() { Id = id, Title = "Hello World" });
helpDeskTicketsRepository.Save();
Console.WriteLine(helpDeskTicketsRepository.GetRecord(id).Title);
var record = helpDeskTicketsRepository.GetRecord(id);
record.Title += ": Updated";
helpDeskTicketsRepository.Update(record);
helpDeskTicketsRepository.Save();
Console.WriteLine(helpDeskTicketsRepository.GetRecord(id).Title);
helpDeskTicketsRepository.Delete(id);
helpDeskTicketsRepository.Save();
Console.WriteLine(helpDeskTicketsRepository.GetRecords().Count());
}

If you were to run the app, a new ticket will be added, its title is updated, and then we delete the ticket. Once this is done we exit the app.

So let’s say we want the tickets to be persistent after exit, however we aren’t sure where we would like to store them? You see that question!? That question is about architecture, not about the design of the application. “However we aren’t sure where we would like to store them?”.

The answer to that question is irrelevant, because we have a repository which simply provides us the operations for handling tickets. We don’t care where they come from or where they get stored.

For simplicity of this article let’s say we want to store the tickets in a JSON file. Here is what the JsonHelpDeskTicketsRepository may look like

public class JsonHelpDeskTicketsRepository : HelpDeskTicketsRepository
{
private IDictionary<Guid, HelpDeskTicket> Records { get; set; }
private const string JsonFileName = "helpdesktickets.json";
public JsonHelpDeskTicketsRepository()
{
LoadTicketsFromFile();
void LoadTicketsFromFile()
{
try
{
Records = JsonConvert.DeserializeObject<IDictionary<Guid, HelpDeskTicket>>(System.IO.File.ReadAllText(JsonFileName));
}
catch
{
Records = new Dictionary<Guid, HelpDeskTicket>();
}
}
}
public void Create(HelpDeskTicket record)
{
if (!Records.ContainsKey(record.Id))
Records.Add(record.Id, record);
}
public void Delete(Guid id)
{
if (Records.ContainsKey(id))
Records.Remove(id);
}
public HelpDeskTicket GetRecord(Guid id)
=> Records[id] ?? null;
public IEnumerable<HelpDeskTicket> GetRecords()
=> Records.Select(x => x.Value);
public void Save()
{
System.IO.File.WriteAllText(JsonFileName, JsonConvert.SerializeObject(Records));
}
public void Update(HelpDeskTicket record)
{
Records[record.Id] = record;
}
}

Now what if you simply construct a JsonHelpDeskTicketsRepository instead of a InMemoryHelpDeskTicketsRepository in our test app code above? Guess what - it works! We didn’t change any of our code, all we had to do was use our new JSON repository and the code uses it just like the in memory repository all thanks to the HelpDeskTicketsRepository interface.

We should avoid architecture making decisions for us as we design, if it does then our code will be coupled to that answer and this will produce non-reusable code and limit us. Architecture should be a choice we make nearest to the end of the project (where possible).

Thank you for reading, I hope this has helped you or has taught you something new. Feel free to provide feedback, positive or negative.

References:
https://cleancoders.com/

Repository: https://github.com/MrApproved/Medium_ArchitectureHelpDeskTickets

Images:
https://www.pexels.com/photo/clear-light-bulb-355948/

--

--