Software Architecture: Recognizing Scopes and Boundaries

Pairing discovery approaches and problem archetypes

Raphaël Tahar
Decathlon Digital
13 min readJul 3, 2024

--

Recognize problems

In the previous post, we discovered the Garbage Can Model and the three streams it is composed of.

This post will highlight two of them: the problem and solution streams. More specifically, how to deal with problems of different scopes and boundaries by pattern-matching the right methodology.

📖 Series table of content

Through 4 posts, this series dissects the complex machinery behind architectural decision-making.

  1. Making Decisions
  2. 👉 Recognizing Scopes and Boundaries
  3. Architecture Decision Record & C4
  4. Social and Organizational Dynamics

But first, let’s take the time to share the same mental model of complexity. It’ll help better appreciate scopes’ archetypes.

The Notion of Complexity 📚️️

Semantics is a great tool for perceiving nuances, but it is only usable by those willing to dig under the surface.

The word complexity comes from Latin and means intertwined (con with & plecto braid). It is often mistakenly understood as hard. But those are two different concepts.

Comprehending something complex can be difficult due to its interconnected facets. Still, a simple thing can be equally challenging to accomplish (for instance, don’t ask me to draw something, even the simplest things).

Simply put (pun intended), the opposite of complex is simple, and the opposite of hard is easy. As Leonardo Da Vinci’s famous quote says: “Simplicity is the ultimate perfection”.

He meant that creating a complex system is sometimes easier than making a simple one. All you have to do is randomly add stuff, and complexity will inevitably arise. Accidental complexity hides in every corner, and finding the most minimal and efficient way to do something is often hard.

That being said, complexity must be delineated to be reasoned about. Even for the brightest minds, it is impossible to think without limits.

This is where the notions of scopes, and boundaries come in.

Scopes and Boundaries 🌌 > 🌍 > 🗾

A global scope is generally defined to detour the boundaries delimiting the thinking process. In contrast, other lower-level sub-granularities allow the representation of relevant pieces that compose the general scope.

Many methodologies are employed to categorize software pieces. At Decathlon, the C4 model is widely embraced (implementation details in the next post).

It divides any software into four sub-granularities, from C1 to C4. Each level encapsulates the previous one (C1 > C2 > C3 > C4).

C4 levels encapsulation

Here is a brief description of each zoom level:

  • C1 — Software System (also called System Context)
    A software system is the highest level of abstraction and describes something that delivers value to its users.
Example of a C1 Software System representation
  • C2 — Container
    Not Docker! In the C4 model, a container represents an application or a data store. A container is something that needs to be running for the overall software system to work.
  • C3 — Component
    A component is a grouping of related functionalities encapsulated behind a well-defined interface.
    An important point to note here is that all components inside a container typically
    execute in the same process space. In the C4 model, components are not separately deployable units.
  • C4 — The Code (Optional)
    Finally, you can zoom in on each component to show how it is implemented as code, using UML class diagrams, entity relationship diagrams, or something similar.

For a given change proposal, the impacted C4’s level(s) will dictate the strategy and mindset to adopt — more precisely, the problem boundaries will. Depending on the clarity of the surrounding software’s interfaces, the methodology to apply will be different.

Here is a model to select the most optimal methodology for a given problem archetype:

Archetypes — Scopes — Methodologies Table

Problem archetypes can be classified into three distinct groups: those with known boundaries, those with unknown boundaries, and new business capabilities. It’s important to note that rows are not mutually exclusive; a problem can have multiple archetypes and may require a combination of methodologies.

The rest of this post will review this table and highlight the perception shifts required to handle these problem archetypes through concrete examples.

Let’s check how to tackle the simplest case of a C3 change with known boundaries through the following example: Replacing a dependency with another in an existing Container, and adapting the codebase to the new dependency’s API.

Reductionism 🧱

To illustrate this C3 change, imagine a project needing to upgrade a dependency version whose API includes a breaking change.

Every usage of the dependency should be updated to use the new API.

This problem’s boundaries are set, forming a finite list of occurrences to change in the code. These changes will impact a defined list of surrounding Components already implemented and used within a known Container and System.

A change in a well-delimited Container’s Component

An ordinary linear approach will do fine to tackle such a change. Just split the issue into sub-tasks and dispatch them to your teammates without further need for synchronization.

Upgrading a dependency represents a flattened list of tasks

This approach handles complexity by artificially simplifying problems. The change is arbitrarily defined and intentionally excludes any others.

This is called Reductionism. It is great when a targeted problem is easily identified and its frontiers can be drawn accurately.

It allows parallelism by shrinking the scope of reasoning. Modulo an alias (and a good type system), migrating the codebase can be done bit by bit, by one or several engineers.

This provides a great way for engineers to optimize their days by contributing when they have a brief window of opportunity. The scope is adapted to a single or multiple persons’ fluid amount of available time.

But what happens when decision outputs should be stitched up at multiple levels to deliver a complete feature?

Holism to the rescue!

Holism 🔗

Now, let’s say that a new decision opportunity aims to refactor several synergizing Components within a Container (Multiple C3 and lower levels impacted).

Multi-Components C3 change in a single Container

Reductionism alone cannot solve that problem. Indeed, the problem’s inner boundaries are unclear, and splitting the work into a flattened list of tasks will not ensure that each decision will be compatible with delivering the expected output.

To find the most optimal way to design that System, one must spot the links and synergies between every sub-portion of the bigger problem.
That’s precisely what Holism allows:

Holism is the interdisciplinary idea that systems possess properties as wholes apart from the properties of their parts. The aphorism “The whole is greater than the sum of its parts”, typically attributed to Aristotle, is often given as a glib summary of this proposal.
- Wikipedia

Holism is about creating links between problems and solutions. In other words, this is about handling complexity and constructing links that stitch outputs together. To help spot key strategic links, Holism allows us to switch from a reductionist output-based mindset to an outcome-based one. Spotting relationships and synergies is essential for creating optimal solutions.

Reductionism vs Holism? System Thinking! 🌌

It might be tempting to think that Holism is the most powerful approach and should be preferred, but it can’t be used without Reductionism, and vice versa. The balance between the two is key to making good architectural decisions.

Any reasoning is about providing perspective and delimiting what part of a bigger whole will shrink our thinking process. No subject can be tackled as an infinite and complete whole, and boundaries must be set.

The decision-making flow is not linear. It must be a back-and-forth process, navigating between the outer and inner layers of the problems and solutions landscape. Each decision opportunity is unique, and it is essential to take the time to distinguish the details deserving to be abstracted from the key strategic items to focus on.

This is where knowledge and experience come in handy; they develop the instinct and help gain the required mental agility to maneuver up and down the chains of interconnected problems and solutions.

Any newly created link might highlight a new perspective, drastically increasing some solutions’ viability while discarding others.

Most of the time, to better frame a problem, we paradoxically need to think of its possible solutions to picture it more clearly. There is no hard frontier between the “what” and the “how”. Even more, they both wield a retro-action on each other.

The combination of Reductionism and Holism is at the heart of a school of thought called System Thinking.

Systems thinking is a way of making sense of the world's complexity by looking at it in terms of wholes and relationships rather than splitting it into parts. It has been used to explore and develop effective action in complex contexts, enabling systems change. Systems thinking draws on and contributes to systems theory and the system sciences.
- Wikipedia

Let’s illustrate this through an example involving already-set System Contexts that need to be enhanced or refactored (C1 and lower levels of existing Systems are impacted).

Multi-Systems C1 change

Picture a new decision opportunity provided by the product teams aiming to smooth the User Experience (UX) over multiple Systems while maintaining high team autonomy. They noticed a surprisingly bad conversion rate from user journeys, including several Systems performing cross-sub-domain redirections, and would like to find solutions to smooth the UX.

To better apprehend the overall flow, here is the end-result diagram representing the reductionism/holism dance composing this decision’s Garbage Can (deprived of its decision-maker dimension; more on this in the next post).

Garbage Can: Reductionism — Holism dance

Let’s break it down 👇

Note: This example had to be complex to make the point. For length reasons, the below demonstration will focus on the decision-making flow rather than deep technical explanations. Understanding them is nice but not essential for achieving the perception shift required to adopt a System Thinking mindset.

01 The first step is to refine your problem; “smooth the user experience” isn’t enough to build something out of it.

Refine!

After discussing this with the product team, we learned that 2 frontend applications have inconsistent User Experiences, which induce heavy conversion rate drops. They’d like to have more consistency in the overall components and designs of the apps.

We also jumped on the occasion to check the technologies used by these two applications. The first is a Svelte app served through Server Side Rendering (SSR), while the second is a React app served through Client Side Rendering (CSR). Besides, they are bundled with different tools (Vite and Webpack).

Note: the initial “smooth the user experience” directive represents only one of the six final problem statements required to make an educated architectural decision. At that point, the five others are unknown and yet to be discovered. Iterative and in-depth refinement is key.

02 The second step is to start thinking of potential solutions while further refining the initial problems with the new information we gathered.

Second iteration: Refine & Solutions

So, the common ways to enforce UX consistency across different applications are:

  • building a Design System
  • creating cross-team governance and guidelines while duplicating component implementations
  • or implementing a MicroFrontend (MFE).

To potentially exclude some options, we’ve reached out to the Product team and asked for their long-term vision concerning the future evolutions of the two applications. They came out with the following answers:

  • Pages will be autonomous and stay served in isolation
  • At some point, components may interact in a cross-application fashion

03 Discovering that pages will be autonomous for a while allows us to merge three problem statements into a unique “Autonomous and linked Svelte and React applications”:

  • SSR Svelte Application
  • CSR React Application
  • Pages will be autonomous and stay being served in isolation
Third iteration: Refine, Merge, Extract & Solutions

And the rendering methods can be extracted into their own problem statement.

Combined with the previously identified MicroFrontend solution, this merge created a new issue termed “Vertical MicroFrontend” (note that a previous solution is now a problem).

New solutions can now be listed under these new problem statements.

Solutions for “Vertical MFE” are:

  • Client Side Include
  • Server Side Include
  • Edge Side Include

Solutions for “Autonomous and linked Svelte and React applications” are:

  • Open Component
  • Web Component
  • Native Federation
  • SingleSPA
  • Iframes

04 After drawing the solution landscape, it’s important to thoroughly understand each piece of technology to identify the main synergies. Comparing each piece would be too tedious, so let’s concentrate on the overall mix of solutions.

Fourth iteration: Sort solutions
Core synergies establishing the final decision

Picking the Native Federation as a solution permits to:

  • Build an architecture supporting both SSR and CSR applications
  • Be compatible with the Server Side Include
  • Switch at any time to a horizontal MicroFrontend
    (doesn’t block the product vision as it can be used to implement horizontal MF as well)
  • Exclude the bundlers' problem
    (since it is available on every major bundler, there is no vendor lock-in like module federation that was an exclusive WebPack feature but recently ported on Vite — without mentioning native federation and module federation v2)

Picking the Server Side Includes solution permits to:

  • Serve the SSR and CSR applications
  • Implement a vertical MicroFrontend
  • Switch at any time to a horizontal MicroFrontend
    (doesn’t block the product vision)

Implementing a Design System permits to:

  • Enhance the UX consistency by centralizing components
  • Be compatible with a switch to horizontal MicroFrontend without impacting the way teams collaborate, deliver, or deploy their systems

Using Web Components to implement a Design System permits to:

  • Avoid components’ definitions duplication while ensuring compatibility with multiple frameworks
    (Svelte and React in our case)
  • Serve isomorphic components
    (that can be run on the client and the server thanks to Declarative Shadow DOM and hydration techniques)

05 The last step consists of capturing all this work in a unique document: the Architecture Decision Record (ADR).

Capturing the decision in an ADR

This part will be addressed in depth in the next post of this series.

Congratulations to those who made it this far! 🎉

What should we keep from this demonstration?

  • The initial problem required further refinement
  • Several rounds of discussions between Engineering and Product were required, at different stages of the process
  • A solution became a problem
  • A problem was excluded by the solution of another problem
  • Some problems were merged, split, or created along the process
  • Capturing all that work in an ADR has a lot of value

In summary, finding the most cost-effective solution required linking it with various other problems and solutions. The entire process involved a dance combining Reductionism and Holism to address sub-contexts and identify important connections between problems, problems and solutions, and among different solutions.

The most optimal solutions for each problem were interdependent, and a significant part of the “what” was tightly coupled with the “how”.

Finally, this flow is captured through an Architecture Decision Record, transforming a complex and recursive System Thinking flow into the expected linear “Dreamt Flow” (CF the previous post “Connecting reality with expectations” paragraph).

The Last Case of New Systems 🆕

There’s one scope of change we haven’t detailed yet: creating a new System within an existing Eco-System.

In such use cases, a pre-existing Ecosystem defines the adjacent boundaries on which a new System should lean to implement a new business objective.

The difference between the previous use case and the new System lies in the encapsulation of that new System. It can be implemented with almost total autonomy. No changes in surrounding Systems are required.

Here, the job is to define the required Containers and internal Components from the ground up. In other words, carve logical sub-pieces with Reductionism and link them together thanks to Holism.

To do so, several methodologies can be combined:

  • Domain Driven Design focuses on the business use cases to apply relevant splits (Reductionism)
  • Event Storming to define user journeys and model them through several technical dimensions. This methodology helps spot key links (Holism) and drastically eases refinement processes. It embodies the “what” and “how” porosities.

I’ll cover these two methodologies in a future series, so we’ll not detail them here. But if you have to design the architecture of a new Software System, you should check them out.

Takeaways 🌯

To sum up, accurately evaluating impacted scopes and granularity helps to select the most effective methodology to find optimized solutions for any given problem.

Simple problems can be tackled with Reductionism alone, while more complex ones require a gradual combination of Reductionism and Holism (System Thinking).

When boundaries between entities are unknown, it is advised to use Domain Driven Design to draw the limits and split the concerns.

Should you design a new business capability, Event Storming might come in handy to bridge the gap between cross-functional stakeholders mastering the business aspects (the “what”) and Engineering teams mastering the “how” while focusing on business use cases and user journeys. It facilitates cross-functional discussions, especially between business experts and engineering teams.

Here’s a summary of the advised methodologies per scope and problem archetype (they are not mutually exclusive):

Archetypes — Scopes — Methodologies

Note: A problem can be of several archetypes simultaneously. For instance, a new business capability with unknown boundaries on a C1 scope will require DDD and Event Storming.

In the next post of this series, we will provide detailed instructions on how to document and store the example of the MicroFrontend architectural decision mentioned above.

Thanks for reading! 🙏🏼
👏🏻👏🏻👏🏻 Give a few claps and “
follow” if you enjoyed this series.

💌 Follow Decathlon’s latest posts on Twitter and LinkedIn and discover our latest stories on Medium 🚀

--

--

Raphaël Tahar
Decathlon Digital

Staff Engineer, Sociotechnical Architect, Author and Philosophy Ënthusiast. Proud dog father 🐶. Opinions are my own.