- Technique 1: Create a Culture of Encapsulation
- Technique 2: Add New Questions
- Technique 3: Change the Old Questions
- Technique 4: A Place for Everything, and Everything in Its Place
- Technique 5: Promote Well-Developed Disciplines
- Conclusion
Technique 4: A Place for Everything, and Everything in Its Place
Making implementation-level decisions early isn't important; in fact, it can be quite destructive. However, promoting early separation of concerns can be beneficial. The easiest way to do this is to steer unrelated or loosely related behaviors into different applications, components, or services. Putting distance between things that shouldn't be connected to one another combats any temptation less-disciplined developers might feel to couple them needlessly.
The classic case is the simple three-tiered application with a client, a server, and a database. Under certain circumstances, a team might be tempted to make that into a two-tiered application. For instance, imagine you're building a custom application for a small company with only one site, a fast LAN, and an existing investment in a super-fast database server. Someone might argue, "Why would we bother with a server?"
There are actually many answers you could give. For instance, it's easier to test an application whose client and server portions are broken into separate components, because you can directly test a server and then mock it out when testing a client (see Figure 8). At least, theoretically that's true.
Figure 8 The three-tiered architecture is theoretically more testable.
A slightly more easily demonstrated reason is that it's easier to add a second interface, such as a web UI, if you have a well-thought-out server (see Figure 9).
Figure 9 The less-theoretical second UI.
You might also lean on all kinds of tropes such as "better performance" and "scalability." Those are generally a waste of time, and only true in a hypothetical sense. You can't often forecast real bottlenecks; you can always find them.
A more fundamental reason to separate client and server logic stands on its own. Most applications exhibit two distinct kinds of behavior: how the application interacts with users, and how the application serves as a central authority on entities and business rules. For example, consider the trivialized design in Figure 10.
Figure 10 Let's just do a fat client!
Within this design is a natural and (I think) pretty obvious division of labor. Figure 11 explicitly shows that Storage and Notification really codify service-type behaviors, even if nobody ever breaks them out into a distinctly hosted service.
Figure 11 There's a service layer here, like it or not.
There's no good reason for code in one of those two roles to be tightly coupled to code in the other. Dividing an application into client and server layers creates a natural barrier preventing that kind of accidental coupling.
With a disciplined team, such a barrier is unnecessary; accidental coupling is rare and persecuted. With an undisciplined team, that barrier can be the difference between whether the application is sustainable or not.
In my experience, the harder a developer pushes for the behaviors of two natural classes to live in a single class, the more important it is to push back. I think that principle also extends to the architectural scale of design: The harder a team pushes for two loosely related things to live in the same component, the more important it is to keep them separate.
That was just an example. The principle of keeping unrelated or loosely related behaviors in separate components applies in many contexts, not just in three-tiered architectures. If the team implementing a feature, project, or product is mature, they will separate unrelated behaviors naturally. If not, you'll have to take that task on yourself until your culture of encapsulation takes hold.