Skip to main content

Command Palette

Search for a command to run...

Clean Architecture Applied

The document generator

Published
3 min read
Clean Architecture Applied

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.

K
kenny1112y ago

Hey, Where would you put your implementations of IDocumentGenerator? In the application layer or in the outer layer (like an infrastructure)?

K
Ken Bonny2y ago

The interface belongs to the application layer as your application needs to know it can generate a document/save the invoice somewhere.

The implementation belongs to the infrastructure layer as this deals with external libraries and the filesystem.

The basic idea is that your application logic (generating an invoice in this scenario) should not be directly tied to things that your app does not control or to separate things that change on a different timeframe.

Let me clarify: writing to the file system does not change very often, so create something that can accept the format you want to save. As this implementation will not change very often, you won't need to worry about writing to the file system anymore for a long time.

Then comes generating a pdf, this might change more often as you might want to update the library/plugin that creates the pdf. I've switched since writing this article from HTML > PDF generation to using the QuestPDF nuget. So this changes more than the filesystem writer, but less than the invoice generation.

Also, this knows about the invoice, but the invoice does not need to know about the format it'll be saved in. So that is another reason why to put this in another class/place. Since it prepares the invoice to be written to the file system, I think it's appropriate to place these two implementations together in the infrastructure layer.

Lastly, the invoice generation has changed the most over the years as this contains the most bugs (relevant to the other functionality), needed the most changes as I discovered more use cases. Since I split it up fairly nicely, I could make easy changes to the invoice generation without impacting the other implementations (too much).

Remember that downstream modules (invoice flows to pdf generation which flows to file writer) might need to change when upstream modules change, but the inverse should not be true.

P.S. I might have gone overboard with answering your question. I think I should start blogging again to get some thoughts off of my chest.

More from this blog

K

Ken Bonny's dotnet, Azure, architecture and feature testing focussed blog

149 posts

Software craftsman specialising in Microsoft technology (mainly dotnet and Azure)