Skip to main content

Fat Foundation

This post describes how to spot and resolve a Fat Foundation module. It's part of a series documenting Helix Smells - some of the common pitfalls of Sitecore development when following the Helix architecture.

Common Symptoms

  • A Foundation layer module contains a conspicuous amount of concrete code.
  • Features are hard to change because of their dependencies on a Foundation module

Why does it occur?

To avoid sideways feature dependencies, code is pushed from a feature into the Foundation layer so that another feaure can reference it.

What problems can it cause?

A Fat Foundation module contains too much concrete code, making it prone to change over time. Multiple features are likely to depend on it. So when you inevitably have to make a change, you risk introducing side effects throughout the system. In short, anything in a Foundation module is difficult to change, and concrete code that's difficult to change is a problem.

The more concrete code a Foundation module contains, the less its dependent features are free to control their own behaviour.

Fat Foundation modules introduce rigidity into the system:
  • You can't change Foundation modules for fear of introducing wide-spread, unpredictable bugs
  • You can't implement custom behaviour in a Feature because too much is controlled by the Foundation layer.
Helix's modular architecture is specifically intended to combat the 'big ball of mud' anti-pattern. As a time goes on, Fat Foundation modules often have feature-specific conditional logic leak in to them. That's a sign that your system is reverting to a big ball of mud.

What's the solution?

Resist the impulse to push concrete code from a feature to foundation module as a way to solve the cross-feature-communication problem.
Use dependency inversion techniques to introduce an abstraction in the Foundation layer that can facilitate communication between features. One feature can implement the abstraction while another consumes it.

Common examples of dependency inversion at work in Sitecore are dependency injection, events/handlers and pipelines.

Can I have an example?

A template in my Orders feature has an "Order Confirmation" field, and an item based on that template in my site has the following value:
"Thanks. Your order has been received, [CustomerFirstName]"
I need to replace the [CustomFirstName] token with the current customer's first name (which lives in the Customers feature).

I'm aware that I'm not supposed to reference the Customers feature from the Orders feature. So I mistakenly decide to move the Customer model and its related service class to a Foundation module. Now the Orders feature can safely get the customer's first name and replace the token in the "Order Confirmation" field.

A few months later I'm asked to change the Customer code to incorporate some new business logic, As a result, I have to refactor code in the Orders feature, potentially introducing new bugs along the way.

By pushing the concrete customer code to the Foundation layer, I allowed an entirely separate part of the system to be affected by subsequent changes.

Adopting  a more 'Helix' mind-set, I think the key is to recognise that the problem doesn't directly concern Orders or Customers at all. Instead, there is an abstraction to be identified - "Token Replacement".

Heres's how I could have resolved the issue.
  • Define a custom pipeline named "TokenReplacement" in the Foundation layer. Create an "args" class that will hold the string that needs to be manipulated.
  • Create a pipeline processor in the Customers feature that replaces the [CustomerFirstName] token in the supplied args string. 
  • Run the pipeline from the Orders feature, supplying the "Order Confirmation" value as it args string.



In this solution, the pipeline acts as an abstraction, where multiple features can provide the implementations of token replacement logic without having knowledge of each other. Changes made in the Customer feature will not require changes in the Orders feature. Additional token replacement processors can be added to the pipeline from various features over time, but the code in the Foundation layer is unlikely to ever change 

Related Reading


This post is part of a series describing Helix Smells. I'm likely to revise it occasionally. If you have any suggestions, contact me on Twitter


Comments

  1. "Use dependency inversion techniques to introduce an abstraction in the Foundation layer that can facilitate communication between features. One feature can implement the abstraction while another consumes it."

    That is completely violates Helix principles, because following official documentation:
    "This principle ensures that changes in one feature do not cause changes anywhere else, and that features can be added, modified and removed without impacting other features." If one feature will depend on implementation of another feature, even not directly but by dependency inversion, we will not be able to remove this feature without impacting other features.

    When you use dependency inversion, you only hide dependencies between features, but still violates Helix principles.


    BUT, I understand your situation. And, to be honest, somewhere I also have no other option and use dependency inversion for features communication inside Sitecore Helix projects. Because sometime hidden dependencies between features is less evil comparing to fat foundation level.

    I think that Sitecore needs to provide official position about violating this principle by dependency inversion.

    ReplyDelete
  2. I disagree with your assessment that the approach violates the Common Closure Principle. Since that principle is primarily concerned with the long term maintainability of code, I'll describe the above example in those terms.

    The Foundation "Token Replacement" pipeline is sufficiently abstract to ensure it is stable, and therefore safe for features to depend on.

    The Customers feature contributes to the pipeline's functionality, without any knowledge of its consumers. I could change code in the Customers feature without needing to change code in other features. In fact, I could remove the Customers feature altogether.

    The Orders feature runs the pipeline without any knowledge of the processors used to do the work. Multiple features can contribute processors to the pipeline and the Order feature code would be unaffected. Equally, the Orders feature can be changed or even removed without requiring code change in other features.

    In my view, this is consistent with the Helix principles and the Package Principles on which they're based.

    ReplyDelete
  3. Correct me, if I wrong, but as far as I understand:
    1) Removing Customers feature will cause presence token [CustomerFirstName] that was not replaced.
    2) Order of processors matters. Customers should be first. Orders should be second.

    Formally features don't depend on each other. But orders processor has hidden dependency on Customers. If you remove Customers processor, Orders will still work, but will stop to work properly, first name will not be replaced.

    Back, to what is written in Helix documentation:
    "This principle ensures that changes in one feature do not cause changes anywhere else, and that features can be added, modified and removed without impacting other features."

    And again, I don't think that this approach is bad, it is good, Common Closure Principle in Helix should be less strict.

    ReplyDelete
  4. On your first point, I'm assuming that you understand the string being passed to the pipeline as belonging to one of the features. From a system perspective, I regard that string as input. Saying that it is part of the Helix architecture is like saying the contents of a Word document is part of the Microsoft Office codebase.

    However, for the purposes of discussion, let’s assume we can consider the string as part of the Helix architecture. In my opinion, it would be most closely aligned with the Project layer. So what you describe as a hidden dependency between features is actually a dependency from the project layer to the Customers feature.

    If I remove the Customers feature, then the parts of the Project layer that reference it will also need to be removed (including the [CustomerFirstName] token). But the code of Orders feature does not need to change.

    On your second point, I think you've misunderstood - there is no processor in the Orders feature, it simply runs the pipeline.

    I purposely chose a simple example where the sequence of processors was not important. But you make a good point that there are scenarios where the sequence makes a difference. I guess in that situation, a technique other than using a pipeline would be a better choice for solving the problem.

    ReplyDelete

Post a Comment