PHP Domain Driven Design Retrospective
Having just completed a major project using DDD in PHP for work I wanted to talk about why using DDD is awesome and some of the things I learned going through the process of building a really big DDD project.
NAMING THING IS F***ING HARD
No, seriously. After about a month of modelling I ended up just throwing the entire model out of the window and starting over. There's branches with 1000s of lines of code that are utterly worth-less just because a few entities had the wrong name. Entities that have wrong names lead to behaviours that have wrong names and then soon enough the entire data model just doesn't sound right.
Perfect example of this is how I had the concept of a "Business"... but is it a Business or are there "Profiles" + "Businesses". Afterall as a SaaS company we have multiple products... so does each Business have a Profile for a Product? But what about big enterprise accounts... So do Businesses or Profiles have parents... Both I guess? ... Ahhhhhh!!
Document ALL THE THINGS
Conceptual Back and forth as you get more information out of stake-holders seemed to be the normal; the biggest thing I regret is not documenting all of the changes. As a sole developer on a DDD project it is tough to make sure you do all the things properly. For example there are 260+ classes, with over 7k lines of test code and a total code base size of over 20k lines. It's complicated. Perfect example of why I wished I documented all the things were feature creeped, cut but then re-inserted again.
In my case it was a concept that a business could have a "pool" of funds that they slowly withdrawal from. A complicated adventure as you might think. It was briefly glossed over but then got cut as; at the time it wasn't needed (months ago)... but now it is needed (Months later :D).
I think I did a pretty decent job at defining the domains for my system however there are definitely always better names. An example of this would be the Domain that is called "BusinessTransactionLedger". Ugh it hurts me to see this everyday right now, but I leave it there as a testiment to think about names first, second and third. It represents a single entity that basically culiminates the account balance of a given business. AccountBalance would have been a way better choice, Lesson learnt.
The reason why Domain Naming is so important is that the terminology you use represent the behaviours you expect from that domain. This helps you read the code and is incredibly helpful when you are trying to find out why something isn't working as intended. This leads me to another lesson learned: Long names are okay. Here's a good example of how I use long names in Commands: CreateTransactionFromVoidedInvoice. It's pretty easy to understand (At least I think) what this command might do.
I used CQS + Event Driven design for this system and I am beyond pleased with the results. Commands are a great tool for isolating and ecapsulating state changes in your system. In my system this proved to be an extremely valuable tool as there are some very detailed use cases we have to support. Something simple like Pay By Cheque, Pay By Credit Card (via stripe)... but what about Pay by ETF? I can simply having a single command that says PayOutstandingAccountBalance and in the handler it processes the HOW based upon that client's preferences. This means I can leverage almost all of the code/domain around Payments and I don't need to copy/pasta anything. Imagine how you might accomplish this with some kind of MVC application? What about when something fails? Stripe/ETF failures?
In my architecture all Commands go to our RabbitMQ Cluster. A command is placed into it's own queue and has a dead-letter-queue attached to it. Failures result in the item moving from the queue to the dead-letter-queue where it can be analyzed and requeued (No state lost). We use idemoptency to make sure we only Charge a customer once.
Event Driven Design
I strictly adhere to the pattern of Commands Represent a state change and Commands result in Event's been emitted. That means it can be a bit of a pain to "wire" certain execution flows. In general Event's represent something that has changed which makes the following flow amazing.
HTTP =(a)> Command => Command Bus => Command Handler => Event => Event Dispatcher => Event Subscriber => (maybe repeat to (a)
This represents a bullet-proof system where nothing will get lost. It also happens to make it very scalable. In my Event Dispatcher I store all events that come from the system (which is a source of ineffiecency but I much prefer that than having to debug something in a distributed system without any context).
Something that I had not realizes is that it can also make a lot of sense when you name things really well.... For example.
Http(CreateNewBusinessAction) => CreateNewBusinessCommand => Bus => HandlesCreateNewBusiness => NewBusinessWasCreated => Dispatcher => [ Email\BusinessSubscriber:onNewBusinessWasCreated => SendWelcomeEmail => Bus => HandlesSendWelcomeEmail, Accounting\BusinessSubscriber:onNewBusinessWasCreated => CreateProductAccounts => Bus => HandlesCreateProductAccounts, ... ]
Instead of having one really big controller we have a nicely decoupled system where one simple action can trigger a whole bunch of business logic, yet it's very easy to follow and even read the Call Stack.
CreateNewBusinessAction, CreateNewBusinessCommand, HandlesCreateNewBusiness, NewBusinessWasCreated, SendWelcomeEmail, HandlesSendWelcomeEmail... Pretty easy to follow!
- NAMING THINGS IS HARD
- DDD is amazing for complicated Domains
- DDD is amazing for simplifying your code paths
- I can write really long complicated sentences using Class names... and that is freaking amazing