-
Notifications
You must be signed in to change notification settings - Fork 1
Software Design Principles
Many of the patterns used for Object Oriented Design (OOD) can be applied to the development of microservices.
One of the main tenets of OOD are the SOLID design principles. This acronym defines a set of design principles that are extremely useful when designing an extensible, maintainable object oriented system. Let’s look at how these might be applied to write SOLID microservices.
The Single Responsibility Principle states that a class should do one thing and one thing only. When a class does too many things, it becomes harder to maintain and evolve over time. This applies equally well to microservices. If you have multiple teams working on the same microservice for different reasons, that may be a sign that the microservice is too big and responsible for too many things. If there are multiple reasons to change, the service should probably be broken into multiple services.
This is a shorthand way of saying that a class should be extensible, but you can’t modify the original or override it’s behavior. This is, in many ways, the essence of encapsulation. There’s a black box that has well defined extension points, but you can’t change what’s in the box. For a microservice, this usually means that you can only “extend” it by interacting with its public APIs. This might mean writing an aggregation or wrapper microservice. This wrapper service may perform some logic before delegating to the original service or it may listen for events from the original and keep another data store in sync. Keep the data store (e.g. a database) private, accessible only via APIs. This is analogous to the way a class only allows access to its private member variables through public methods.
The main idea here is that subclasses maintain the correctness of the contract defined by the parent class. This means that subclasses shouldn’t override a method that does one thing to do something completely different. Code that uses the superclass should be able to use the subclass and not know the difference. Microservices don’t really have the notion of inheritance, but the idea is still applicable here. If I decide to completely replace the implementation of a service, the API should be maintained. That is, other applications shouldn’t know or care that the service has been written or rewritten in Java, Node.js or C#. One implementation can be transparently substituted for the other.
If you’ve ever worked with a class or interface that has 20 public methods, and you only needed to call one, you’ll understand the usefulness of this principle. Instead of creating one big uber-interface, create lots of smaller, narrowly defined interfaces. A single underlying class may implement all of them, but the calling code doesn’t have to know that. The same is true with microservices. You may think it’s easier to write a single API that returns the full domain object, but for many callers it’s overkill. For clients that only care about one or two fields, that’s extra network overhead as well as the time required to query for that data in the first place. Instead, create narrowly targeted interfaces, each defined with the client’s needs in mind, not those of the service writer. This may mean you have more API endpoints to maintain, but they’ll be targeted and easier to change over time.
The main idea is that objects should depend on abstractions, and not low level implementations. These abstractions are injected, not created by the class that needs them. Most microservice architectures rely heavily on a service discovery framework. Such a system allows services to find and call each other without having to hard-code a lot of configuration. If service A needs to call service B, it asks the service discovery framework to route the request accordingly. The service discovery framework has essentially injected the dependency on service B into service A.
Good design principles work, regardless of the language or specific system to which they’re applied. Hopefully you no see that the SOLID principles apply equally well to object relationships or microservices. The next time you run into difficulty figuring out how to design a particular microservice, take a step back. Think about the concepts you’ve learned for object oriented design and how they might apply to your microservice. I think you’ll find that they translate pretty well.
“Keep It Simple, Stupid!” – I would add some extra exclamation marks (!!!!) to try to keep this in your mind. The simpler your code is, the simpler it will be to maintain it in the future, and of course, if other people see it, they will thank you for that. The KISS principle was coined by Kelly Johnson, and it states that most systems work best if they are kept simple rather than making them complex; therefore simplicity should be a key goal in design and unnecessary complexity should be avoided. My advice is to avoid using fancy features from the programming language you’re working with only because the language lets you use them. This is not to say that you should not use those features, but use them only when there are perceptible benefits to the problem you’re solving.
Don’t Repeat Yourself – How many times do you see that there are similar codes, in different parts of a system. Well, this principle is formulated by Andrew Hunt and David Thomas in their book The Pragmatic Programmer that every piece of knowledge must have a single, unambiguous, authoritative representation within a system. In other words, you must try to maintain the behavior of a functionality of the system in a single piece of code. In the other hand, when the DRY principle is violated it is called as WET solutions, which is stand for either Write Everything Twice or We Enjoy Typing. I know this principle is very useful, especially in big applications where they are constantly maintained, changed and extended by a lot of programmers. But you also should not abuse of DRYing all things you do, remember the first two principles KISS and YAGNI, in first place.
“You Aren’t Gonna Need It” – Sometimes, as developers, we try to think a lot in the future of the project coding some extra features “just in case we need them” or “we will eventually need them”. Just one word… Wrong! I’ll repeat it this way: You didn’t need it, you don’t need it and in most of the cases… “You Aren’t Gonna Need It”. YAGNI is a principle behind the extreme programming (XP) practice of “Do the Simplest Thing That Could Possibly Work”. Even when this principle is part of XP, it is applicable in all kind of methodologies and own processes of development. When you feel an unexplained anxiety to code some extra features that in the moment are not necessary but you think they will be useful in the future, just calm down and see all the pending work you have at this moment. You can’t waste time coding those features that maybe you will need to correct or change because they do not fit to what is needed, or in the worst scenario, they will not be used.
A key principle of software development and architecture is the notion of separation of concerns. At a low level, this principle is closely related to the Single Responsibility Principle of object oriented programming. The general idea is that one should avoid co-locating different concerns within the design or code. For instance, if your application includes business logic for identifying certain noteworthy items to display to the user, and your application formats such items in a certain way to make them more noticeable, it would violate separation of concerns if both the logic for determining which items were noteworthy and the formatting of these items were in the same place. The design would be more maintainable, less tightly coupled, and less likely to violate the Don’t Repeat Yourself principle if the logic for determining which items needed formatted were located in a single location (with other business logic), and were exposed to the user interface code responsible for formatting simply as a property.