Clean Architecture Applied – The document generator

Now that I have time information about my workdays, the next step is to generate the invoice. There I face another problem. I want to generate a PDF, but I need an easy format to test against. Let me first take inventory of what I have:

  • I have an interface IDocumentGenerator that takes the workdays information
  • I found the DinkToPdf library that converts HTML to PDF

I learned that when I generate the PDF with DinkToPdf, the PDF has metadata included such as the date and time the PDF was created. So, this foils my plan to compare the created byte arrays. It’s a good thing that I have smart friends that remind me of the decorator pattern and pointed out that HTML is a lot easier to check.

I use a HTML templating engine to generate the HTML that represents an invoice. I found Fluid to be the most versatile library to generate the HTML I need.

To create the PDF, all I have to do is create a class that takes the HTML document generator, gets its output and let DinkToPdf do its work.

public interface IDocumentGenerator
{
    (string extension, byte[] document) Generate(Invoice invoice, Customer customer, BillableItem[] billableItems);
}

public class FluidHtmlDocumentGenerator : IDocumentGenerator
{
    public (string extension, byte[] document) Generate(Invoice invoice, Customer customer, BillableItem[] items)
    {
        using (var htmlStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(TemplateLocation))
        using (var htmlReader = new StreamReader(htmlStream))
        {
            var htmlTemplate = htmlReader.ReadToEnd();
            FluidTemplate.TryParse(htmlTemplate, out var template);
            var invoiceDocument = GenerateHtml(customer, items, invoice, template);
            return ("html", invoiceDocument);
        }
    }
}

public class DinkToPdfDocumentGenerator : IDocumentGenerator
{
    public DinkToPdfDocumentGenerator(IDocumentGenerator htmlGenerator)
    {
        _htmlGenerator = htmlGenerator;
    }

    public (string extension, byte[] document) Generate(Invoice invoice, Customer customer, BillableItem[] billableItems)
    {
        _htmlGenerator.Generate(invoice, customer, billableItems);
        // use DinkToPdf to generate PDF
    }
}

When testing the code, I just use the FluidHtmlDocumentGenerator, convert the byte array into text and compare that to the output I expect. I have one test that generates a PDF so I can check that the DinkToPdfDocumentGenerator generates the correct output. All I do there is compare the length of the array returned. I can change the test slightly so it writes the output to a file. This allows me to see that I generate a PDF that looks like the rendered HTML. This is a manual process since I haven’t found a way to automate this. Which is just a small inconvenience as I verify that my layout is correct with the HTML tests.

Clean Architecture talks about being independent of libraries. Hiding these libraries behind interfaces allows me to implement other classes with different libraries. That is how I can easily change HTML or PDF generation libraries without my InvoiceGenerator needing any change at all. It’s a very flexible and extendable structure.

For example, in most of my tests, my setup of my document generator looks like this: new FluidHtmlDocumentGenerator(). In my actual production code, this changes to: new DinkToPdfDocumentGenerator(new FluidHtmlDocumentGenerator()).

The next infrastructure part is the customer repository, which I will write about next week.

Advertisements

Clean Architecture Applied – The work repository

Now that I have the general steps to get to an invoice, I can start working on the individual components that reside in the infrastructure layer; starting with the work repository.

The domain logic would need time data, which reminds me of the Repository pattern. So I create an interface IWorkRepository that gets work days between two dates: Task<WorkDay[]> GetWorkDays(DateTime start, DateTime end).

[DebuggerDisplay("{DebuggerDisplay}")]
public class WorkDay
{
    public WorkDay(string customerTag, DateTime day, int secondsWorked, bool billable)
    {
         Day = day;
         TimeWorked = TimeSpan.FromSeconds(secondsWorked);
         Billable = billable;
         CustomerTag = customerTag;
    }
    public DateTime Day { get; }
    public TimeSpan TimeWorked { get; }
    public bool Billable { get; }
    public string CustomerTag { get; }
    private string DebuggerDisplay => $"{CustomerTag} - {Day:d}: {TimeWorked}h ";
}
The private DebuggerDisplay field is a little workaround to allow easy string manipulation (especially dates) to get nice debug information.

This would allow me to use a fake work repository when testing to start verifying other behaviour. However, I learned about Flurl, a nice library that allows me to build HTTP requests easily, but also allows me to use it for testing. The next thing I did was build the TimeCampWorkRepository, referencing the actual TimeCamp API URL. Flurl allows me to use the TimeCampWorkRepository in the tests so I can verify that the JSON I retrieve from the API is correctly deserialised and mapped to the WorkDay structure. I got that JSON from actually calling the API and saving the response. This allows me to verify my own code to the point right where the actual API call will happen and how the returned information is handled.

This also ties into the Clean Architecture principle that the database, or more general data store, should be pushed to the infrastructure layer. I put this code in its own project to clearly separate this concern. Putting an interface between the actual call to the TimeCamp API and the control flow of the invoice generator allows me to add other data sources if I should ever change the app I use to track my time.

Next up is the document generator.

Clean Architecture Applied – Applying the principles

Before I dive into the technical details, let me give you a little warning:

This solution is over-engineered, I could write this fairly straight forward and be done in a few hours. The experiment here is to implement this app using the guidelines from Clean Architecture. So expect too many projects, a bit of plumbing code and a lot of interfaces.
Also, I cannot make the full code available as this code contains secrets (such as my customers). It would also allow anybody to generate invoices in my company's name. I will not be making this code easily available. Ever.
I thought about making an example repository to share with the world. I do not think I would give that repository the same attention I give this code, it would not represent the quality that I put into my work.
If you or your company need help with improving code quality, I offer consultancy services and training. Head over to More Than Code for more information.

The problem domain is not a difficult problem, so let’s check out how I structured the code to get it all working.

The first step is to get the data from the TimeCamp API, which I need to transform into days worked so I can sum them and then put them into a format that I can save as a PDF-file.

I started by creating a solution with a project InvoiceGenerator.Core. I can also name it .Business, .Domain or .Rules, pick something that clearly communicates what is in the project. At the same time, I create an InvoiceGenerator.Tests project, because all important parts need to be tested well.

Because this is not a large project, I only create one test project. I believe in testing use cases, such as “generate HTML invoice from TimeCamp data”, that test a lot my own code (I’ll get back to that in a minute). This allows me to change the inner working of a module, without losing any functionality. My tests will guard the functionality. That is why my tests will always be very specific. This test knows about the TimeCamp API and that the invoice will be in the HTML-format.

What I mean by “my own code” is all code that I write. It encompasses everything from domain logic, internal plumbing and all code right up to the point it goes out of my hands, such as to a database, the network or the hard drive.

Domain and Use Case

Invoice generator

The first test then sounds easy: generate an HTML invoice from TimeCamp data. It all starts in the core business logic. For that I create a class InvoiceGenerator in the Core project. This will orchestrate the interactions between all the little components. It determines the month the invoice should be generated for (the previous one). Then it gets the data about the days I worked. Finally, it generates invoices for all customers I worked for.

public async Task<Invoice[]> Generate()
{
  var startOfPreviousMonth = _dateProvider.Now.StartOfPreviousMonth();
  var endOfPreviousMonth = startOfPreviousMonth.AddMonths(1).AddDays(-1);
  var workDays = await _workHoursRepository.GetWorkDays(startOfPreviousMonth, endOfPreviousMonth);
  var billableWorkDays = workDays.Where(day => day.Billable);
  var invoices = new List<Invoice>();
  var previousInvoiceFileName = _invoiceReader.GetPreviousInvoice();
  var description = _dateProvider.Now.ToString("MMMM").ToLower();
  foreach (var daysForCustomer in billableWorkDays.GroupBy(x => x.CustomerTag))
  {
    var customerTag = daysForCustomer.Key;
    var customer = GetCustomer(customerTag);
    var billableItems = GetBillableItems(customer, daysForCustomer, startOfPreviousMonth);
    var invoice = GeneratedInvoice(previousInvoiceFileName, customer, description, billableItems);
    previousInvoiceFileName = invoice.FileName;
    invoices.Add(invoice);
  }
  return invoices.ToArray();
}

The _dateProvider is there to make injecting a fake date a lot easier. It’s quite difficult, read impossible, to change the DateTime.Now date. Which makes testing different months a lot easier.

The previous invoice is used to calculate the new invoice number, this ensures that I can keep numbers nice and sequential.

Adding immutable objects

Dealing with state is complex. Most bugs are introduced when unexpected data influences the flow of code. That is why I made most objects that pass through boundaries immutable. Objects from the InvoiceGenerator back to the console (which will become my UI, I will go into more detail in a future post on this aspect) or from the DocumentGenerator back to the InvoiceGenerator are being created by the originating code (InvoiceGenerator, DocumentGenerator).

public class Invoice
{
  private readonly string _description;
  private string _extension;
  private readonly int _id;
  private Invoice(int id, DateTime invoiceDate, string description, byte[] document)
  {
    _id = id;
    _description = description;
    InvoiceDate = invoiceDate;
    Document = document;
  }

  public byte[] Document { get; private set; }
  public DateTime InvoiceDate { get; }
  public DateTime ExpiryDate => InvoiceDate.AddMonths(1);
  public string Number => $"{InvoiceDate:yyyy}{_id:0000}";
  public string FileName => $"{Number}-invoice-{_description}.{_extension}";
  public static Invoice FromPrevious(DateTime invoiceDate, Customer customer, string description, byte[] document)
  {
    var id = GetId(invoiceDate);
    return new Invoice(id, invoiceDate, description, document);
  }
}

This way the document or invoice can’t be updated after it’s created. Not by my or any other code. I make it easy on myself by creating a static factory method that can take care of creating an object. I could just use the constructor, but I think the method is a bit cleaner to read.

The principles

The core domain consists of the general flow of the application. Getting workdays, grouping them per customer, transforming them to billable records and filling that data into invoice templates.

It then hands control to several plugins (such as a IWorkRepository or a DocumentGenerator). This is part of the layer approach. It also allows me to experiment with different ways of getting the workdays and different template engines to generate an invoice. It also means that I can easily test different components.

I had already thought about how I would tackle each issue, but let’s go through it step by step. Starting with the work repository.