Welcome to the third instalment of the series “Design Patterns”. After discussing the singleton pattern in the previous article, this time I want to talk about the command pattern. It organizes the way we work with repositories. The extra layer it provides gives us the ability to organize communication with repositories in a much more readable and reusable way. Plus, the CQRS pattern has its origins in the command pattern.
What and why? And the problem.
There is no application without data. This statement is true for about 99.99% of existing applications. Think about it for a moment - can you name me a real application that does not use data? If so, please feel free to let me know.The way we approach data operations is through repositories. These are pieces of code that handle communication with the data provider.
The problem is that we often tend to call repository methods directly from our code or through the services. While this approach does its job, it is far from flexible or reusable. The command pattern changes this approach a bit. With the command pattern, we create commands that group actions together at the domain level. So, the program performs an action using a command. And this command has the task to control the communication with the repository or repositories. It can even call it more than once. If it is necessary. The program just says what kind of command it wants to execute.
The code presented in this post can be found here, where the projects in the solution correspond to the pattern names.
The old way
That was easy, yes. But it made the code rather cluttered. The other drawback was that with this approach, the process was not repetitive and could not be easily undone. I am not a fan of creating the same pieces of code over and over again. But you do not have to take my word for it. Let us take a look at an approach without the command layer.
You can find the code in the CommandPattern project in the Program.cs file. In the executable part, the BasicApproach method is being called. The code for this method can be seen below.
Do you like this code? Chances are you do. It's perfectly fine. The code is easy to read, does its job and it's pretty compact. What's more, it works. So, what's wrong with it? The problem with such an approach usually occurs when we create something complex. When the solution is small, this kind of coding is perfectly fine. When it gets bigger however, the extra layer becomes very useful. This layer allows us to combine operations into commands by making the operations more general. In addition, with this command pattern, it is very easy to implement the undo scenario.
But before we dive into coding the additional layer, let us take a quick look at what the current code does. The process is quite simple. There is a shopping client repo, which represents the shopping map of the client. We also got a product repo, which is responsible for managing the shop products. The client adds two types of products to his card and checks the content of the card at the end. Let’s run it and see the effects.
As we can see, the result is a card of six items - two of Prod1 and four of Prod2. We can see that it works. Now it's time to implement it using the command pattern.
As I said before, we will try to make the implementation as generic as possible. The first thing we need to do is create an interface that will be responsible for the command methods. I will call it I Command. Shocking, is not it?
In the approach I present, the command requires only three methods. Do - which performs the desired action. CanDo - which tells whether the action can be performed. Undo - which provides the ability to undo the command action. These three methods are all generic and in no way bound to specific actions.
The second part of the infrastructure is a command manager. Its task is to execute commands and the possibility to undo them. In the code, I put it in the CommandManager.cs file.
It has two methods and one property. The property is used to store the command execution history. Essentially, we can use it to undo all our actions. The two methods are self-explanatory. Invoke - invokes the command passed as argument. Undo - undoes all the invoked commands.
That's it for the utility code for executing command patterns. Now let us see how the command implementation looks like. In the code we have two commands – while one is responsible for adding the product to the customer card, the second one is deals with increasing the product quantity. We will take a closer look at only one of the two commands, namely the first one.
Add To Client Command
This command can be found at the AddToClientCommand.cs file.
The first element we need to pay attention to is the constructor. When we create a command object, we are expected to provide it with all the tools it needs to accomplish its task. In this case, the command needs the two repositories and the product object.
When it comes to implementing methods, they all follow the same approach. When processing repos, perform all the actions required to execute the command. Let me show it with an example. To execute the AddToClient command, we need to decrease the inventory of products and add them to the client card.
If we want to see if the AddToClient command can be executed. We need to check if there are still products in the warehouse. This is how you need to think of the code when you use this pattern. This way you create sets for the operations in the repositories that correspond to the commands you need.
The Command Approach
Now that we have all the elements in place, let us see how we can use commands in our program. To do this, please open the Program.cs file again and look at the CommandApproach method.
The code has been changed a lot, because we are using the commands now. In order to use a command, it needs to be created. var addToClientCommad1 = new AddToClientCommand(shopClientRepo, productRepo, product1); This line creates a command, which adds to the client. The adding operation is to be done with the two repos provided here and on a product1 - an object that was also supplied. To execute this command, we need to call the Invoke method and use this command as a parameter in a following manner: manager.Invoke(addToClientCommad1).
The same approach needs to be applied for each command. The advantage is that the command class is reusable. We have used it twice. Also, there is now a command manager. This hand object gives us the possibility to save commands that have been called and to undo them if necessary.
As a bonus, please remember that we are not limited to one manager. We can adjust its number to the scenario we want to create.
To be sure, let us run this code and see if it does the same job and if it can undo the changes we made. Spoiler alert... it does.
The command pattern has given us an extra layer of control. Pun intended. Yes, the code base is larger, but we gain reusability and aggregation of operations. And obviously we end up with the undo, we cannot forget about that.
What do you think about this approach? Do you this it is worth it? Let me know at firstname.lastname@example.org.