Special Case Pattern
Apiumhub
Posted on August 14, 2019
Don’t let your specific case increase your code complexity: Special case pattern
In our day to day, there is our application up and running and suddenly a new requirement is required. So we start typing more code to fit that new request. If we start typing without paying enough attention, we could add more complexity that needed. Before that, sometimes revisiting the design patterns may help you to find a better solution that fits exactly with your needs and to avoid some possible bugs or problems related with that new code.
An Example: Blog Application
Let’s start with a running blog which has 2 uses cases (we don’t care about the rest):
- BlogPostApplicationService
- create blog post (title, text, authorId, date)
- List all posts by date
- AuthorApplicationService
- List all authors (name, last name, picture)
Our PO asks for a new functionality: some articles will be created automatically by a scraping bot.
- This bot will create new articles
- These articles will appear when listing all articles
- Bot won’t have a profile page
What’s the issue? Every article needs an authorId and bot is not an author.
Solution: Our first approach may be to set the authorId as optional. This way, when the bot creates a new article it doesn’t need to set any authorId.
This solution doesn’t fit very well. To fit a specific problem (a case where we don’t have the authorId), we have modified an important rule of our domain (every article should have an author). Besides, since authorId is optional, we have to check if it is null in several places (we are repeating over and over the same code –> hence we are generating a log of duplication).
Also, when writing the queries to get all the posts with the author information, a left join must be used (not an inner join) since the bot entries won’t return any author data.
Another solution we could come up with: define the bot with a constant in our code (like 0 for example. Doing this we could avoid having the authorId as nullable (now all entries would have an author) but that constant id wouldn’t have any meaning in our domain. Anyways, we would end up with our code full of if ($authorId === BOT_ID)
to fit that specific case (WET everywhere again). This is also a not optimal solution.
With these simple solutions we have increased the complexity of our code just to fit a specific case. We have added several null checkings in our code and we already know what happens with nulls ( see the famous the billion dollar mistake). Also there is a bad smell we should be aware when we are doing the same check here and there. Time to check if there is a pattern that can help us with this problem.
Special Case Pattern
A subclass that provides special behaviour for particular cases. By Martin Fowler (https://martinfowler.com/eaaCatalog/specialCase.html)
This pattern fits very well with our problem. The idea is to create a subclass of Author called Bot to handle our special case: our bot should be able to write a post as an author but with its own specifications, for example we won’t show its profile page. This is a neat solution because our code doesn’t need to be changed (Bot is already an Author)
Once we have detected a suitable pattern, how can we implement it in our project?
Single Table Inheritance
Represents an inheritance hierarchy of classes as a single table that has columns for all the fields of the various classes. By Martin Fowler (https://martinfowler.com/eaaCatalog/singleTableInheritance.html)
How can we implement this inheritance in our database? Relational databases don’t support inheritance but we can use it through ORMs which implements inheritance (in doctrine for example).
Using single table inheritance , the different classes (in our case Author and Bot) are mapped to a single table with a column (‘type’ in the picture) where it defines the type of class for each entry. When we retrieve that entry, the ORM will map the entry to its own entity hiding the final implementation to us.
Let’s implemented it altogether
Using doctrine, we create the table Writer where Bots and Authors will be stored.
/**
* @ORM\Table(name="writer")
* @ORM\Entity()
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="type", type="string")
*/
class Author
{
protected $id;
protected $name;
protected $lastName;
protected $picture;
}
/**
* @ORM\Entity()
*/
class Bot extends Author
{
}
We could add a database migration to create a bot user in our system with a predefined id:
$bot = new Bot("BOT_ID", "Bot", "", "");
From now on, the bot will use this constant id (“BOT_ID”) and the problems we detailed earlier are fixed:
- No more “if author is null” checks in our code
- When querying the database, all entries will have some valid author data.
To tell the client about the difference between the authors, when mapping the entities to DTOs we could easily add a flag:
class AuthorDto
{
public $id;
public $firstName;
public $lastName;
public $picture;
public $system = false;
}
class BotUserDto extends AuthorDto
{
public $system = true;
}
If system is true, frontend can avoid adding a link to the bot profile which is not needed by our requirements.
Conclusion
We have avoided that a true special case of our application increased our code complexity (with the bugs and risks it carries) and with the use of a couple of patterns our code is apparently cleaner. Last but not least, we also avoided the problems related with nullability (at least in this occasion).
The post Special Case Pattern appeared first on Apiumhub.
Posted on August 14, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.