Last week we looked at Using Entities in different Bounded Contexts. Entities are very valuable objects within an application as they hold a lot of power and responsibility. However, as an application grows, it’s important to add structure and constraints to prevent responsibility from leaking outside of that particular context.
So far, Cribbb has Bounded Contexts for
Groups. I’m sure there will be more functionality to add to those Bounded Contexts as we progress with development, but for now I want to begin to look at the next Bounded Context of the application.
An important part of the functionality of Cribbb will be centred around Discussions. In today’s article we’ll start to think about modelling the Discussion functionality of Cribbb.
Do we need a new Bounded Context?
As I mentioned above, I will be encapsulating this functionality in a new Bounded Context called
Discussion. However I think it’s important to look at that decision and show my reasoning.
Discussion Bounded Context will be intimately linked to the
Group Bounded Context because the
Group Bounded Context holds the responsibility of permission as to whether a User can post to a thread.
Whilst these two Bounded Context have that close relationship, I think it’s better to separate the functionality into two distinct Bounded Contexts.
Discussion Bounded Context relies on the
Group Bounded Context for permissions, but the
Group Bounded Context does not need to be concerned with the
Discussion Bounded Context at all. I think when you have this division of concerns, it’s much better to make the clear separation.
Secondly, discussions will have a different granular breakdown of the roles of users within that context. For example, the person who creates the thread would be an Author, whereas other members of the discussion would be Contributors or Lurkers.
And thirdly, I think it is important for clarity to keep these two distinct aspect of the application separate. As the application continues to evolve I think it’s highly likely that these two Bounded Contexts will diverge as we add more functionality.
Identifying the important concepts
It can be tempting to just jump straight into the code when you are looking to build out functionality like this, but planning your design is an extremely important part of the process.
It’s very easy to just start writing code with your head down, but before you know it, you’ve created something that does not satisfy the requirements of the project.
Identifying the important concepts from the spec should inform the decisions you will make and the code you need to write.
For this application, there are a couple of important business rules that should dictate how the application functions:
- A Thread should be created as part of a Group
- A User must be a member of a Group in order to create a Thread
- A Thread should be created with an initial Post
There will of course be other, more subtle and nuanced business rules that we will need to enforce once we get into the details of the implementation, but we can cross that bridge when we come to it. Whilst planning is important, analysis paralysis doesn’t move the project forward.
The division of responsibility
So we’ve decided that we will model the discussion functionality as a new Bounded Context, and we’ve established the broad business rules that we need to enforce.
The next part of the planning process is designing the division of responsibility and how our business rules should be enforced.
Object Oriented Programming is all about encapsulating important concepts and protecting the invariants of objects. We can therefore leverage the characteristics of Object Orientated Programming to enforce our business rules.
One useful Object Oriented technique for enforcing business rules is ensuring that all properties of the object are required on instantiation. For example, if all users should have an email and a password, you can enforce this rule by ensuring that the
User object requires those dependencies on construction. The native characteristics of PHP will thrown an
Exception if those requirements are not satisfied.
We’ve seen this technique in action when building out the User Entity in The User Entity and The Ubiquitous Language.
A second technique is to use a Factory in the form of a Factory class or a Factory method on an existing Entity. A Factory has the single responsibility of creating new objects.
We can use this technique to ensure that a
Thread is created as part of a
Group by an existing
User of the
Group by handing responsibility to the
Creating a new
Thread object in isolation doesn’t make sense within the context of the business rules because a Thread should not exist in isolation. By using this technique we are leveraging the characteristics of Object Oriented Programming as well as staying close to the Ubiquitous Language of the application.
What do we need to build?
Now that we’ve got a fairly good idea of the functionality and what we are trying to achieve, we can begin to think about what we need to build in order to satisfy the requirements.
Thread object will be the main Aggregate Root and will act as the gatekeeper to all other objects within this Bounded Context. No other object makes sense outside of the context of a
Thread, and so this not only keeps us close to the Ubiquitous Language, but it will also make our code a whole lot simpler.
Thread will consist of many
Post objects. The
Post object will belong to a
User and will hold the content and meta data around that particular post.
As I mentioned above, there will also be a more granular breakdown of the Users within the Discussion Bounded Context. I haven’t fully come to a decision on this, but it isn’t integral right now. I’ll delay that decision to later in the process.
I’m sure we will need a whole host of other Domain Objects and Services as we continue to build out this portion of the application, but instead of biting off more than I can chew, I’ll focus on building out the core functionality first.
I think it is really important to just take a brain dump when you are looking to tackle important functionality like this. It’s very tempting to just jump straight into the code, but the investment in this early design process will pay dividends in the future. Clear design is essential for clarity.
Modelling important functionality as separate Bounded Contexts is usually a good idea. If one aspect of a Bounded Context does not care about the other, it’s probably a good idea to make it two distinct Bounded Contexts. All Bounded Contexts will have associations with other Bounded Contexts, and so just because two aspects of your application are intimately linked, doesn’t mean they can’t be separate.
Before you jump straight into writing code, it’s often a good idea to identity the important concepts of the functionality you are attempting to build. By thinking about the business rules of the application and the division of responsibility of the objects you will need, you are far more likely to come up with a solution that meets the goals of the business.
There is no shame in delaying writing code to ensure you have the correct plan. By building out the core functionality and satisfying the requests of the business, you are creating a solid platform to evolve and grow the application.
This week’s article was my brain dump of what I need to think about to build the next section of the application. Next week we will continue to build out the
Discussion Bounded Context.
To view a full listing of the tutorials in this series, click here.