How to simplify complex logic using declarative coding in C#.

Satjinder Bath
codesimple
Published in
5 min readMar 3, 2016

Every now and then we get to implement very complex logic and there are various ways to manage the complexity. In my 3 Posts series, I have discussed how we can handle complexity introduced by the multi-tenancy aspect of the business logic.

I recently came across a logic, which was going to create a maze of if/else conditions in my code, which I could not let happen. Because of the complexity in the decision making, context-driven dependency injection could help, but that would lead to business logic leaking to the infrastructure code, which is a really bad code smell. Considering container more on the infrastructure side and like it to handle the aspects of the application, not business logic. I did not either want to introduce a heavyweight business rules engine, but I really like the readability side of the business rule engines. I can not really pinpoint which pattern or model I ended up implementing, but the closest I could find is the Adaptive Model explained by Martin Fowler, however for a different purpose in mind. Complementing the adaptive nature with strategy pattern did the trick for me. That was more or less what and why I went that path, now we will explore a bit of how.

Example - Pizza Maker:

Let's look at an example of an advance pizza store where our code would guide the pizza maker bots. So we have got a selection of pizzas such as “Meatlovers”, “Satay Chicken” etc. This store also offers gluten-free and nut free variations for some of the pizzas, where possible. It will choose an alternative ingredient if possible and in some cases, it can not have the variation e.g. Satay Chicken does not have a nut-free option. So we have a current implementation like this:

https://gist.github.com/satjinder/438ef548a4ee4eb26473cb9f90b7fd2e#file-1processorder-cs

Process order will take a request to prepare the number of pizzas, for each pizza it will call PizzaMaker. PizzaMaker uses RecipeFactory (instance provided by IoC) to apply ingredients. RecipeFactory provides an instance of the Recipe class based on the pizza name. So far it's all nice and simple. But complexity has to live somewhere and in this case it has been pushed down to the IRecipe implementation. Let's look at an implementation of the MeatloversRecipe.

https://gist.github.com/satjinder/438ef548a4ee4eb26473cb9f90b7fd2e#file-2recipes-cs

So MeatloverRecipe is making few decisions here, based on the gluten free or nut free options, it selects a different ingredient. Similarly, other options will do a similar setup based on the corresponding recipes. It can be argued that it delegates the responsibilities well, but at the cost of complex code and also not so readable. If I have to work out, what recipes offer nut free or gluten free options, I will have to go the implementation and make sense of it. You may have noticed that we have identified a common piece of code and extracted to the abstract base class, employing DRY principles. Now this means less code now but will lead to more pain later on when we have to change one of the recipes and make sure it still works with others. This does not mean DRY is bad, the problem is in the way we implemented it. So how can we simplify it and improve readability?

Cleaner Alternative:

Let’s look at an alternative implementation and where we will have cleaner implementation and better readability, that leads to more maintainable code. The following code snippet is something that will reflect the business requirement in so easily readable code that I have seen non-programmers getting hang of it.

Taking some guidance from the Adaptive Method and applying a strategy pattern, we can extract a fair chunk of the complexity out to the declarative code or data (json/xml). Which can be a portable set of business rules, applied with very simple code.

There is a similarity in all the Recipe implementations, they all apply ingredients in a particular sequence. Based on the options selected, they would choose a separate set of ingredients. So if we extract this behavior as a strategy we can come with the following strategies for all the pizzas:

  • ApplyStandardRecipeStrategy
  • ApplyGlutenOptionsRecipeStrategy
  • ApplyGlutenAndNutOptionsRecipeStrategy

Assuming that we store all the ingredients in a data store with some metadata such as if the ingredient is nut free or gluten free etc. Recipe Strategies will get all the ingredients from the data store and then apply them based on the criteria it deals with. Simplest would be the ApplyStandardRecipeStrategy, which will just apply the first available ingredient in the groups with the assumption of first being the default. Out of these three, ApplyGlutenAndNutOptionsRecipeStrategy is the most complex and following is the implementation of it:

https://gist.github.com/satjinder/438ef548a4ee4eb26473cb9f90b7fd2e#file-3applystrategy-cs

By outsourcing the logic to these strategies, we just need to work out the application of the appropriate strategy at the run time. Here we can replace the RecipeFactory with RecipeStrategyFactory. Unlike RecipeFactory, it will use little bit complex criteria to select the strategy. In the following implementation, the criteria have been extracted as static data as a set of rules. We can even go further to extract the rules out of the code and put them in an external data store to work in Adaptive Method.

https://gist.github.com/satjinder/438ef548a4ee4eb26473cb9f90b7fd2e#file-4strategyfactory-cs

What we have achieved is the simpler code, easier to add more recipes as long as they can be served by existing strategies. Above all, the strategy selection criteria have made the code quite readable to even nontechnical people. It is even clearer in highly complex scenarios. Recently I did implement this logic for a complex piece of work. To my delight, the piece of code was even referenced by the Testers as well. You know what would be really awesome if it could be extracted and stored in a way that business can add/remove options using the given strategies.

Originally published at codesimple.blog on March 3, 2016.

--

--

Satjinder Bath
codesimple

A husband, father and a technologist. I am a strong advocate of domain-driven designs and event-driven architectures.