osteel's blog Web development resources

Building a PHP CLI tool using DDD and Event Sourcing. Part 2: the model

Been here before?

You can also subscribe to the RSS or Atom feed, or follow me on Twitter.

Transaction processing flowchart preview

Extract from the transaction processing flow (made with Excalidraw)

The model is where the software meets the domain.

It is a technical expression of the domain that informs the development of the software while being understandable by the experts, who can validate it.

The goal of the model is to identify and express the use cases and constraints that will be built into the software, using schemas and diagrams. It is where we extract the essential concepts from the domain and where we consolidate the ubiquitous language.

In this series

In this post

Modelling

There is no clear recipe to follow when it comes to modelling. Instead, there is a plethora of tools that we can leverage to express the model, some more or less suitable depending on the task at hand.

It's about finding the ones that will successfully inform the implementation of the software.

Transaction flowchart

I wasn't sure where to begin and looking online for answers was mostly adding to the confusion, everyone seemingly using a different approach.

As I felt inertia creeping in, I decided to have a go and to start with what was screaming at me the loudest – transaction processing.

I checked the domain article again and as I was going through the various rules and examples it contains, I realised that I was looking at a bunch of "if this then that" scenarios.

I picked up a marker and drew the following flowchart on a whiteboard:

Hand-drawn flowchart on a whiteboard

How it started

While I couldn't fit in all the details, the diagram covered all the scenarios listed in the domain and connected them in a logical way, without obvious gaps.

Satisfied, I made a digital version of it:

Transaction processing flowchart

Transaction processing flowchart (made with Excalidraw – click here for a higher definition)

Most of the blue rectangles were quite high-level (e.g. "NFT rules"), but as I knew I could find the particulars in the domain article, I decided that it was good enough for now.

User stories

Looking at the flowchart, I realised I needed users to report their transactions in a way that would fit the logic.

So I started listing the reporting needs as user stories:

  • As a user, I want to specify whether a transaction is a transfer so it is processed correctly
  • As a user, I want to specify whether an asset is an NFT so the transaction is processed correctly
  • As a user, I want to specify whether a transaction is a swap so it is processed correctly
  • As a user, I want to specify whether a transaction is a disposal so it is processed correctly
  • As a user, I want to specify whether a transaction is an acquisition so it is processed correctly
  • As a user, I want to specify whether a transaction is an airdrop so it is processed correctly
  • As a user, I want to specify a transaction's fee so it is processed correctly

I went through the domain article again and added some implicit use cases not represented on the flowchart:

  • As a user, I want to specify the cost basis of a transaction so it is processed correctly
  • As a user, I want to specify the disposal proceeds of a transaction so it is processed correctly
  • As a user, I want to specify the market value of a fee so it is processed correctly
  • As a user, I want to specify whether an acquisition is a gift so it is processed correctly
  • As a user, I want to specify whether an acquisition is a gift from a spouse or civil partner so it is processed correctly
  • As a user, I want to specify whether an acquired NFT was minted from other NFTs so the transaction is processed correctly
  • As a user, I want to specify whether an airdrop should be counted as income so it is processed correctly
  • As a user, I want to specify transaction hashes so I can easily access transaction logs
  • As a user, I want to attach a note to a transaction so I can be reminded of the context

Note that I didn't mention hard forks – the reason is that they can be reported as regular acquisitions with a 0 cost basis.

I then added some more generic needs:

  • As a user, I want to report amounts in pounds so I can obtain numbers in the currency I need to pay my taxes in
  • As a user, I want to report amounts in other currencies so I don't need to convert them to pounds myself

That got me thinking about the submission process, for which I came up with these stories:

  • As a user, I want to submit a CSV file so I can process my transactions
  • As a user, I want to submit an Excel file so I can process my transactions
  • As a user, I want to submit a previously submitted file with new transactions so I don't need to submit new transactions only
  • As a user, I want to select a tax year so I get numbers for this tax year only
  • As a user, I want to specify whether my activity is considered trading so I can obtain numbers for the correct tax regime

In turn, that led to the reviewing process, where the user goes through the information returned by the software:

  • As a user, I want to see my capital gains so I can complete my tax return
  • As a user, I want to see my income so I can complete my tax return
  • As a user, I want to see my non-attributable allowable costs so I can complete my tax return

I thought of a couple more use cases for this one, extracted from the domain:

  • As a user, I want to know whether I am within the 30 days following the end of the tax year so I am aware I should wait before completing my tax return
  • As a user, I want to see my revenues beyond allowances so I don't need to calculate this myself

Finally, there's a fourth category of stories that only came to me later, during the code design phase – exporting the results. It's well and good to display the results in the terminal, but as a user I also want to be able to save the corresponding figures in a file for easy access.

I added the following stories:

  • As a user, I want to export my revenues and non-attributable allowable costs to a CSV file so I can complete my tax return
  • As a user, I want to export my revenues and non-attributable allowable costs to an Excel file so I can complete my tax return
  • As a user, I want to export my revenues and non-attributable allowable costs to a PDF file so I can complete my tax return

But not all stories have the same importance. To classify them further and get a better sense of what would need to be built and when, I resorted to a tool I had used before – a user story map.

User story map

User stories and user story mapping come from the Agile world and are not inherently DDD practices. But they provide valuable insights that we can use in a DDD context nonetheless.

I'm not even sure whether they are better suited for the model or the domain, but as they help both the experts by listing the user needs in a way they can understand and the developers by sketching the outlines of the software, the model seemed like a good fit.

A user story map is a visual representation of user stories. It's a way to logically group, sort and prioritise stories that helps us identify and separate the parts that we need to build together from the stories that we can address at a later time.

This is the user story map I came up with for the application. It's interactive, so you can have a look around and click on the various stories:

Interactive user story map (made with Miro)

The blue rectangles are the activities – the high-level tasks the user needs to complete. They match the four categories identified earlier – report, submit, review and export.

The yellow rectangles are steps – they represent subtasks the user has to go through to complete an activity.

Finally, the other rectangles are the details of each activity and step. They're essentially user stories, sorted by priority and broken down by iterations. Here, I've defined two – the MVP and the backlog.

The user story map provides both a roadmap for me to follow and a way to keep track of my progress. Moreover, it gives me an idea of the future bounded contexts and aggregates of the application, two key concepts of DDD.

I'll leave the aggregates aside for now as I consider them an implementation detail, but as bounded contexts can inform the model, I'll cover them now.

Bounded contexts

I'm going to be honest here – I am unable to give you a precise explanation of what a bounded context is. Like most things DDD, everyone seems to have their own definition (here is one, for instance).

But the gist of it is to separate the various parts of an application and describe how they relate to each other. That latter part is usually done with a context map, which I drew below:

Context map

Context map (made with Excalidraw – click here for a higher definition)

Notice that the activities identified in the user story map have become bounded contexts.

We can also see that the "Report" context is external to the application, as the transaction spreadsheet must be created and completed upstream. Its relationship with the application is one of customer-supplier – the provided file (supplier) must meet the expectations of the application (customer).

On the other hand, both the "Submit", "Review" and "Report" contexts are internal to the application, and the tax year object bridging them is likely to be the same for all. In other words, they have a shared kernel.

This is the extent to which I will cover bounded contexts. While they can be useful to model the domain, in this case I defined them for the sake of the exercise more than anything else.

Bounded contexts are often worked on by separate teams and considered to be distinct models with their own ubiquitous language. But this is a one-man show, and the size of the model doesn't warrant breaking it up anyway.

When it comes to this series, the context map is just an extra way to represent and think about the application.

Ubiquitous language

As the meeting point of experts and developers, the model is also where they use a common language – the ubiquitous language.

We can go through the diagrams and user stories to start establishing a glossary of key terms:

  • 30-day period: The 30 days following the end of a tax year. Transactions occurring during that period may still count for the tax year that has just ended, because of the 30-days rule of pooling;
  • 30-days rule: A pooling tax rule whereby assets acquired within 30 days of being disposed of must be matched with those disposals;
  • Acquisition: The fact of acquiring an asset, in whichever way it may be;
  • Airdrop: The fact of receiving an asset in the context of a promotional operation;
  • Allowable cost: A cost that can be deducted from other revenues, attributable to either an acquisition or a disposal;
  • Allowance: An amount under which a tax regime doesn't apply;
  • Application: The software processing the transaction spreadsheet and outputting results;
  • Asset: A property whose acquisition or disposal is a taxable event;
  • Capital gain: A profit realised when the proceeds of an asset disposal are greater than the cost basis of the acquisition, for assets falling under the Capital Gains Tax regime;
  • Cost basis: The cost of acquisition of an asset, in fiat terms;
  • Disposal: The fact of disposing of an asset, in whichever way it may be;
  • Disposal proceeds: The amount obtained for an asset disposal, in fiat terms;
  • Fee: An amount paid in exchange for a service;
  • Fiat: A government-issued currency;
  • Gift: An asset either received or given away for free;
  • Hard fork: A protocol update leading to the split of a blockchain and the creation of a new asset;
  • Income: An amount falling under the Income Tax regime;
  • Loss: A loss realised when the proceeds of an asset disposal are less than the cost basis of the acquisition, for assets falling under the Capital Gains Tax regime;
  • Market value: The value of an asset at a certain point in time, in fiat terms;
  • Mint: The action of creating an NFT;
  • NFT/Non-Fungible Token: A type of asset that isn't subject to pooling rules;
  • Non-attributable allowable cost: A cost that can be deducted from other revenues, that can't be attributed either to an acquisition or a disposal;
  • Note: A piece of free text attached to a transaction;
  • Pooling: A set of tax rules applying to certain types of assets;
  • Remaining amount: Any amount left after one of the pooling rules has been applied;
  • Report/reporting: The user process of recording a set of transactions in a spreadsheet;
  • Revenue: Generic term to design any type of income, be it dividends, capital gains, or anything else;
  • Review/Reviewing: The user process of going through the application's output;
  • Same-day rule: A pooling tax rule whereby assets acquired or disposed of on the same day must be matched with those acquisitions and disposals;
  • Section 104 pool: An abstract container used as part of the pooling rules where the acquisitions of an asset are flowing into to calculate its average cost basis;
  • Section 104 pool rule: A pooling tax rule whereby the acquisitions of an asset are flowing into a pool to calculate its average cost basis;
  • Spouse or civil partner gift: An asset either received or given away for free, from or to a spouse or civil partner;
  • Spreadsheet: A list of transactions reported by the user;
  • Submit/Submission: The user process of submitting a transaction spreadsheet to the application;
  • Swap: The fact of exchanging an asset for another;
  • Taxable event: An operation which is subject to a tax regime;
  • Tax return: The document users have to submit to HMRC to declare their revenues;
  • Tax year: The UK fiscal year, running from April 6 to April 5;
  • Token: A property whose acquisition or disposal is a taxable event;
  • Trading: Designates a user's crypto activity being considered a financial trade, thus falling under the Income Tax regime;
  • Transaction: Any crypto activity as reported by the user;
  • Transfer: The fact of moving an asset from one wallet to another, when both wallets belong to the user.

We came across most of these terms before, but by compiling them in this way, we crystallise them as words and expressions to use everywhere, including in the code. We formalise them.

Closing thoughts

I had the same difficulties approaching the model as I did the domain. Domain-Driven Design is very much an experience-driven discipline and starting without proper guidance can be a bit overwhelming. It took me a while to identify the right tools to express the model in helpful ways, and as I'm writing this I'm still not sure whether I went too far or not enough (although I suspect the latter).

That being said, I trust that the next steps will expose any gaps left in the model and the domain, and force me to revisit those to try and get a clearer picture.

The next instalment of this series will cover the code design of the application, which flows directly from the model.

Subscribe to email alerts below so you don't miss it, or follow me on Twitter where I will share the next posts as soon as they are published.

Enjoying the content?

You can also subscribe to the RSS or Atom feed, or follow me on Twitter.

Last updated by osteel on :: [ ddd userstory userstorymapping boundedcontext contextmapping ]

Comments