Swift Package Manager Series 04|Code boundaries suitable for inclusion in Packages
What is really difficult is how to avoid moving the coupling relationship into multiple new modules as it is.
One of the most common mistakes many teams make when they first start doing modularization is:
First decide to remove the module, and then think about why the module exists.
So the final result is usually “copy the original coupling to more directories”. On the surface, there are more modules, but in fact the dependencies have not changed, and are even more confusing.
So what I want to talk about in this article is: what kind of code is suitable to be put into Package, and what are the most common structural misjudgments when splitting.
1. To determine whether it should be split, don’t look at the number of files first, but first look at whether the boundaries naturally exist.
Whether a module is worth taking out depends on whether it has natural boundaries.
The so-called natural boundaries are usually reflected in:
- It assumes a relatively single set of responsibilities
- Its dependency direction is relatively clear
- It does not need to know much about the host project context
- It is likely to be reused in multiple scenarios in the future
If these conditions are not met, just because “this folder is a bit big now”, then it is likely to be separated just to continue coupling in another place.
The first principle of modularity is “the boundary already exists, I just make it explicit”.
2. The ones that are least suitable for removal in the first batch are often large business blocks that are strongly coupled with the page.
When I first started doing this, when I was doing modularization, the things I was most tempted to dismantle were:
- Home page
- Account Center
- Payment process
- Content details
Of course these modules are important, but they are also often deeply coupled with these things:
- Routing -Host App life cycle
- Page status
- bury the point
- UI resources
If you start from these places for the first split, the boundaries will not be clear, and it is easy to appear:
- The module itself also needs to rely on the host in reverse
- In order to share something, multiple packages reference each other
- The public layer is polluted by the business layer
Therefore, what is more suitable for demolition for the first time is usually the “block with the clearest boundary”.
3. Code suitable for inclusion in Package usually has three typical forms:
1. Basic ability module
For example:
- Log
- Network encapsulation
- local cache
- Configuration reading
The common features of these capabilities are clear responsibilities, page independence, and large reuse space.
2. Domain model or domain service module
For example:
- User model and user information query
- Content domain objects and content warehouse
- Order field logic
This type of module is more business-oriented, but as long as the boundaries are clear, it is also suitable for package development.
3. Stable UI basic component module
For example:
- Design system basic components
- Universal style system
- Several interactive components without business coupling
The premise is that they are really stable and do not steal page-specific semantics.
4. A very common misunderstanding: splitting by directory rather than by dependency direction
Many teams fail in modularization. On the surface, it seems that there is too little dismantling, but in fact it is closer to the wrong dismantling method.
The most typical questions are: How to divide folders in the past is now how to divide modules.
For example:
Home/User/Common/Utils/
On the surface it looks like a module, but in fact you will soon encounter:
Homedepends onUserUserdepends onCommonCommonactually contains a bunch of business logic mixed inUtilsIn the end, it became a trash can with everything stuffed in it.
This shows that what is being split is the directory, not the boundary.
A truly more stable disassembly method should be viewed from the direction of dependence:
- Which modules can only be relied upon downwards
- Which capabilities are the public underlying
- What are the business area layers?
- Which ones are exclusive to the host layer?
Only when the dependency direction is clear first, module splitting will be stable in the long term.
5. Don’t hide dependencies for modularity
When some projects dismantle modules, in order to avoid circular dependencies, they start to use various compromise methods:
- Extra large public module
- Draw agreements everywhere
- Put a “bridging layer” in the middle
These techniques are not impossible to use, but if they are just to “make the module diagram look good”, it is easy to hide the real coupling instead of solving it.
So what I care more about is:
- Whether the current dependency relationship is business reasonable
- Are the modules separated due to different real responsibilities?
- Or is it just to circumvent the limitations of the tool?
Really healthy modularity is when coupling is clearly placed in the right direction.
6. A practical judgment: If this module is taken away, will the host project still understand it?
This is a very common way of judging myself.
If you pull out a candidate module, ask yourself:
- Is the host project’s understanding of it still clear?
- Does the external caller only need to know the interface without knowing a lot of internal details? -Whether it is still a complete concept after leaving the host
If the answer is “yes”, then it’s usually better suited as a Package. If the answer is “no”, it’s probably still just a bunch of implementation details inside the host.
7. When disassembling for the first time, it is better to dismantle less than to dismantle it into pieces.
When doing modularization for the first time, it is easy to fall into the impulse of “Now that we have started to dismantle it, let’s dismantle it more.” But one of the most common failures in medium-sized projects is that modules are too fragmented.
Once the module is too fragmented, the cost will rise quickly:
- The dependency graph is more complex
- Compilation relationships are more difficult to understand
- Version and boundary management is more troublesome
- When reviewing, you have to jump back and forth across many modules
So when splitting for the first time, I prefer:
- First remove a few modules with clear boundaries
- observe several iterations
- Decide whether to continue refinement
The biggest fear of modularity is that the first step is to break the system into many small pieces that seem independent but are actually intertwined with each other.
8. Conclusion: What is suitable for inclusion in Package is “ability with clear boundaries”
To put it in shorter form, I would say:
The key to judging whether a piece of code is suitable for inclusion in a Package is not how many files it has, but whether it has relatively clear responsibility boundaries, dependency directions, and independent semantics.
So what really matters is:
- dismantle
- According to what boundary?
- Is the dependency direction clearer after disassembly?
Only if these three things are established in advance, modularization can really reduce complexity, rather than rearrange complexity.
读完之后,下一步看什么
如果还想继续了解,可以从下面几个方向接着读。