Sitemap
iamprovidence

iamprovidence

👨🏼‍💻 Full Stack Dev writing about software architecture, patterns and other programming stuff https://www.buymeacoffee.com/iamprovidence

Designing Boundaries: The Art of Splitting Systems

8 min readApr 27, 2026

--

Press enter or click to view image in full size

Have you ever wondered what the difference is between a folder and a project? While it may seem minor at first, a simple decision can save you from future headaches, make dependencies clearer, and make your system much easier to maintain.

In today’s article, we’ll talk about what a component really is, how to separate components properly, and why those boundaries matter. We’ll look at the difference between organizing code with folders, enforcing structure with projects, and isolating systems at the application level.

Without any further ado, let’s begin.

What Is a Component?

Regardless of the scale of your architecture, it is always the same: you have components, and a relation between them.

A component can depend on, collaborate with, contain, expose, or hide other components.

But what is even a component?

It can be a class, a folder, a project, a module, or even a microservice. It is something that owns its data, exposes behavior, hides implementation details, and can evolve independently (ideally).

Therefore, let’s have a clear definition:

A component is a cohesive part of a system that does one thing well and interacts with the rest of the system through explicit contracts.

However, a folder is not automatically a component. A project is not automatically a component. A boundary is what makes it one.

Where Should You Draw the Boundary?

The moment you draw a boundary between components, you create an internal structure, a public interface to interact (API), and a direction of dependencies. And that’s where architecture begins.

There are different reasons why you can create a boundary.

You can separate by technical concerns — business logic, infrastructure, UI. That’s the classic layered approach.

Press enter or click to view image in full size

You can draw boundaries around business capabilities (Orders, Payments, etc), as encouraged in Domain-Driven Design.

Press enter or click to view image in full size

You can also separate by rate of change, following the Single Responsibility Principle. If the parts change for different reasons, they deserve a boundary.

There’s a well-known trilemma in architecture: releases, responsibility, or code reuse. As with other trilemmas, you have to choose two principles and sacrifice the third one 😒.

Place your fingers on two nodes, and the line between them shows what you had to deal with.

There are other valid reasons to create a boundary. You might split based on team organization, scalability requirements, or deployment needs.

The difficulty isn’t writing the code. The difficulty is deciding where the boundaries truly belong.

The real problem with architectural boundaries is that almost any decision can be justified if you argue well enough.

See yourself, even with simple classes like the ones below, it’s not always obvious how they should be structured.

Should you keep the interface and its implementation in the same module (N-Layered)? Is it worth splitting abstractions from implementations (Ports and Adapters)?

Press enter or click to view image in full size

Would it be better if everything related to a feature lived together (Vertical Slices)? Or could you isolate each module completely to avoid unnecessary references between components (Divine Architecture).

Press enter or click to view image in full size

The key is this: boundaries should solve a real problem — organizational, technical, or business — not exist just because you can have them.

Define a boundary whenever you need to separate responsibility, ownership, and concerns. If boundaries are well designed, the system can evolve safely and predictably. If they’re wrong, everything becomes tightly coupled and hard to change.

In any case, you probably won’t get boundaries right from the first try 😅. That’s normal. Architecture is iterative. You refine it by better understanding the domain, noticing where friction shows up, and refactoring when the pain becomes real.

How Do You Define a Boundary?

If you’ve decided to split your system into components instead of building one big monolith, that’s already a good sign 😅. But the moment you break a system into modules, you also need to introduce dependencies between them — and that’s where the real challenge begins. Every dependency is a form of coupling. And coupling is what determines how painful change will be.

You can try to keep dependencies manageable, enforce rules, and define clear contracts — but it’s still possible to mess things up. A few careless references, shared abstractions in the wrong place, or circular dependencies, and your “component” starts behaving like a tangled monolith again.

Get iamprovidence’s stories in your inbox

Join Medium for free to get updates from this writer.

That’s why it’s not enough to just split components. It’s equally important to understand the different ways to separate them properly, so boundaries stay real — and change stays cheap.

— folder boundary

The simplest and lightest way to organize a system is by grouping components logically with folders.

It gives you a clean structure with no overhead and is simple to refactor. For small systems and prototypes, this approach can work well because it keeps things fast and flexible.

But let’s be honest about the downsides 😒. There is no real enforcement — only convention. Dependencies are easy to violate, and the structure can be broken in seconds. With folders, you may not even notice when one component starts using functionality from another. Over time, this often turns into spaghetti code.

— project boundary

Physical separation with projects is a different thing.

The difference between a folder and a separate project is major. A folder is just an organization. A project actually gives you real boundaries.

With separate projects, you get compile-time enforcement, clear dependency direction, explicit contracts, and better ownership.

They let you control dependencies between components, avoid cyclic dependencies, move to a deployable NuGet library, and boundaries can be easily enforced with architectural tests.

Press enter or click to view image in full size

Additionally, you can make use of access modifiers like public and internal that allows you to control visibility and encapsulation inside an assembly. However, it only works between modules, but not within, and does not fully protect you from architectural drift.

The problem with projects is that they introduce more setup, more coordination between teams, and refactoring across projects becomes heavier. If your application grows too complex, you can still organize projects further using folders or even separate solutions. Just remember that going too far with project separation can make the codebase fragile and harder to maintain.

There are no strict rules to it. Try it yourself and see what suits you best. As always, it is a trade-off between reliability and complexity. My advice here is not to create too many projects unless you really need them.

This approach fits best for medium-to-large systems where architecture starts becoming real.

— application boundary

An application boundary — such as microservices or N-tier — means running in a separate runtime process.

This provides true isolation, scalability, and even technology freedom. With separate processes, you can not even allow references to the wrong component.

Boundaries are enforced strictly through API communication — either inside the same machine (for example, calling another module through its public interface, IPC, CLI) or over the network via HTTP, gRPC, or events.

While strong internal APIs alone can dramatically reduce chaos, it is still possible to violate rules by affecting application outputs such as a database, cache, queue, and so on. If components bypass those entry points and access internals directly, the boundary is meaningless.

As always, there is a cost to this level of separation, like performance degradation, serialization overhead, network complexity, distributed system challenges, versioning headaches, and operational complexity.

This level of separation should be used only when the business genuinely justifies it — not because microservices are fashionable.

Summary

Architecture is the art of drawing boundaries between components — and living with their consequences.

  • A component is a cohesive part of a system that does one thing well and interacts with the rest of the system through explicit contracts.
  • A component can be a function, class, folder, module, project, or microservice.
  • There is no universally correct structure. You can separate components by: technical layers, business capabilities, rate of change, team ownership, deployment, or scalability needs. Every choice is a trade-off.
  • You can define boundaries logically with folders, or physically with a project or even a separate application.
  • Every boundary adds protection — but it also adds complexity.
  • Even when boundaries exist, they can still be violated. Shared databases, shared files, shared caches, or careless references can silently destroy isolation.
  • Boundaries are not just about structure. They require discipline within the team, clear conventions, and architectural tests.
  • The higher the boundary, the more expensive the mistake.
    — at the folder level, the cost of bad boundaries is inconvenience.
    — at the project level, it is a refactoring effort.
    — at the service level, it becomes deployment coordination and team friction.
  • Separating too early can be just as harmful as not separating at all.
  • You won’t get boundaries perfect on the first try. Architecture is iterative. You refine it as the domain becomes clearer and friction reveals where separation truly belongs.
  • Sometimes poorly chosen boundaries can be more damaging than having no boundaries at all.
Press enter or click to view image in full size

Let me know in the comment section how often you organize your system 💬 Clap if you like the article👋 Support me with a coffee ☕ Follow to receive more ✅ And see you next time! 😜

--

--

iamprovidence
iamprovidence