Coupled Microservices

There are a few primary high-level advantages and one major drawback that occur to me when I think about making the transition from monolith to “microservices” (I use quotes here for these reasons). Some of the major advantages are logical isolation, ease of deployment and independent scaling on your infrastructure. I will go into more detail on each of these, but that is not the point of this post. My goal here is to point out how you can easily lose most of the advantages that microservices confer and be left with only the drawback(s).

Logical Isolation

Logical isolation means that you have separated one logical, cohesive “chunk” of your application into its own independent system. It reduces cognitive overhead for the developers working on that system by allowing them to focus on the function, structure and features of this service alone. This service can be independently managed by a small team without as much regard to the system as a whole. This makes these teams more autonomous, reducing the burden of the “mythical man month” problem and enabling faster parallel development of the system as a whole.

Ease of Deployment

Because each service is smaller and maintains a high level of independence from the rest of the system, services can be deployed much more easily. The communication between teams and team members that is necessary around each deployment is reduced (ideally to nearly zero). Changes can become much smaller, and smaller is significantly better in software development. Smaller changes can also be deployed more often thus enabling one of the major goals of devops — continuous delivery. More deploys means that we significantly reduce the average amount of time between a developer introducing a bug to the codebase and the discovery of that bug. This leads to a much lower mean time to resolution as the developers that worked on the buggy portion of the codebase worked on it more recently. Lower mean time to resolution leads to happier customers and happier, more productive developers who are freed up to work on more important things. All in all, ease of deployment should be a major goal of any agile team and having many smaller, independent services helps to enable this.

Independent Scaling

Independent scaling refers to the fact that smaller services can more easily be split up and grouped according to usage and infrastructure requirements. Services that receive more usage and traffic can be given more raw horsepower to do their job. Services that are used infrequently can be given much less in the way of compute resources, or they can be grouped together. All in all, having many small services gives you a lot more flexibility in how you build your infrastructure, often enabling quite a bit in cost savings.

Drawbacks

One “drawback” to microservices is that when you transition from monolith to microservices, you move from a centralized system to a distributed system. This is usually touted as an advantage, but if you lose the other advantages, this quickly becomes a liability.

Distributed systems are inherently more complex, overall, than centralized systems. They are more difficult to reason about. Consistency of data comes to the forefront as an issue that you need to worry about. Tracking down errors becomes more difficult as your logging systems are (by default) also now distributed. In a distributed system, you need to be much more deliberate about your logging and event tracking systems to track data and events as they propagate through your system (so you can quickly isolate errors and track down bugs).

It can also be more difficult (and expensive) to find developers with expertise in distributed systems. While developers are increasingly becoming accustomed to working in such environments and thinking in a distributed manner, it is still a relatively new way of thinking for your average-sized company.

How To Be Left With Only The Drawbacks

When you have the advantages of logic isolation, ease of deployment and independent scaling the issues that come with a distributed system are often worth the trade off. In addition, they can be mitigated to some extent by deliberate effort and a thoughtful implementation of your architecture and the support systems that you provide your developers and operations people.

So how do you ensure that you gain the advantages of logic isolation, ease of deployment and scaling?

The simple answer is: decouple your services.

You must ensure that your services remain independent to the highest degree possible. There are many ways to achieve this, and much of the Old Wisdom™ of good software engineering and systems design can be applied here. But I want to point out one of the most important factors: versioning.

Version your services. Without versioning, you end up with a highly coupled distributed system and you lose many of the most important advantages that microservices provide. You essentially end up with a distributed monolith, the worst of all possible scenarios

If your services are not versioned, you lose logical isolation. Developers on one team have to think about how their changes will affect developers on another team. This increases their cognitive load and developers are resigned to thinking about the system as a whole again. Cross-team communication is now much more critical and you lose much of the team autonomy and team parallelization that microservices are supposed to allow.

Deploys become a coordination nightmare. Teams will spend hours coordinating their deploys so as to ensure no interruption to other services. Once again, cross-team communication is increased and autonomy is decreased. Cross-team blaming is also reintroduced, as you will very often have to release different apps simultaneously. When something fails, both sides are apt to blame the other (even if politely…). If you have versioned your services, however, they can (and usually will) be released independently.

What about independent scaling? Well, the good news is that even if your services are deeply coupled, you can still scale the machine(s) they rely on independently. However, if your services are still tightly coupled, how much money will you actually save every year through independent scaling? Does this savings outweigh the cost of the added cognitive overhead (read: more developer time/developers) that your poorly designed distributed system has created?

Conclusion

The conclusion is simple: if you are building out a microservices architecture, decouple your services or else you will likely end up with a more costly distributed monolith. Implement good systems design and thoughtful abstractions and by all means, version your APIs.

Coupled Microservices

Scaffolding

In the talk “Inventing on Principle“, Bret Victor talks about the importance of establishing a guiding principle that defines your work.

His guiding principle is that ideas are important. He believes that it is ideas that give meaning to our lives. But ideas start off fragile — they need to be nurtured and enabled to grow. They need an environment in which to mature. In order to nurture ideas, he emphasizes the importance of creators having an immediate connection with their creations and he offers several brilliant examples of ways that we, as engineers, can establish immediacy in our creative loops.

In doing this, he demos several amazing environments that he has built for various engineering disciplines. All of these environments give immediate and useful feedback to the creator.

Some tools of this nature may exist for your editor, and you absolutely should dig up what you can find to increase the quantity of feedback and decrease the time to receiving it. However, it also occurs to me that in many cases this immediate feedback may be a non generalize-able special purpose kind of feedback. It could be considered ‘scaffolding’ for your project.

There are very few engineering disciplines, after all, that output a product that doesn’t need some kind of ephemeral single-use material in its construction. Effort is often put into building something that will be thrown away when then final product is complete.

I hate wasted work. I think all programmers hate wasted work. I especially hate it. Even though I love what I do, I am always so hesitant to build something when there is a possibility that it won’t ever be useful for anything. I have a low tolerance for risk when it comes to my time.

However, I would like to identify what kinds of things I should build that could considered “scaffolding” for my projects; those things which can’t be automated or toolified or used on every project, but which will make the development process for that project much smoother, feedback faster and development more fun. I would like to constantly look for ways to maximize immediate visibility into my software. And I would like to be more okay with throwing stuff away at the end of a project once it has served its purpose.

Scaffolding

PubSub Vs Observer Pattern

Both PubSub and the Observer Pattern allow a central source to send out messages (these could include data, event notifications etc) that then cause/allow various other objects to update or change. There is a simple but subtle difference, however.

In the observer pattern, the publisher (subject/observable) has knowledge of the subscribers (observers). In the PubSub pattern, any service that needs the information being published can willingly subscribe to that publisher. The publisher has no knowledge of the specific subscribers, but simply publishes the message to a channel. Depending on the implementation, it does not need to be a public channel. Amazon SNS, for example, allows you to restrict the subscribers to specific servers or networks.

You can think of PubSub as being akin to a broadcast model. Think of a radio station “publishing” its content on the airwaves, with no knowledge of who will tune in. The “subscribers” are those who have chosen to tune their radio to that station.

The observer pattern is more akin to a home delivery newspaper. The publishing company must know the name and address of every subscriber ahead of time to deliver their content.

Both have advantages and disadvantages. For example, in the PubSub model your publisher is allowed to be a much more abstract service. It does not need any specific (concrete) knowledge of the other services or objects in a system. All it needs is to know how to publish its message when it is time. Everything else is left up to the other services.

The observer pattern allows more centralized control. While the subject (publisher) needs to have intimate knowledge of all observers, it also has control over who receives its messages. It can centrally decide to stop publishing updates to specific observers, or it can choose to add an observer.

PubSub Vs Observer Pattern

Dynamic Typing Vs Static Typing Vs Weak Typing Vs Strong Typing

A common misconception is that dynamic typing (as in variable types… not as in your keyboard…) is synonymous with weak typing and that static typing is synonymous with strong typing. This is an untrue assumption. Dynamic typing is the opposite of static typing and weak typing is the opposite of strong typing and both of these sets represent entirely different concepts.

Dynamic Vs Static Typing

A statically typed language is one in which you must declare all variable types before compilation (and in a few rare cases, interpretation). Types are checked at compilation thus allowing the compiler to detect any errors with your variable types. A dynamically typed language, on the other hand, will check your variable types at run time. Dynamically typed languages are thus able to dynamically assign variable types depending on the input. This often saves programmer time and makes for simpler code.

While a statically typed language can save system resources by compiling the code ahead of time thus eliminating the need for type-checking and resource allocation at run time, there are some memory advantages to a dynamically typed language. A dynamically typed language can (in some cases) save some memory as the interpreter is able to assign only what is necessary to fit the data entered. In a statically typed language, on the other hand, the programmer must predict ahead of time the maximum amount of space that some variable input (for example, from a file or database entry) will consume and reserve that space ahead of time. If a smaller amount of data enters the program, any extra memory that has been reserved for this data is wasted. Overall, however, a program written in a statically typed language is typically more efficient than a program written in a dynamically typed language. The advantages that a dynamically typed language offers primarily have to do with making the programmer’s job faster and easier (at the expense of some extra system resources). In many circumstances in our modern world, this trade off is well worth it.

Weak Typing Vs. Strong Typing

The first thing that should be noted when talking about a weakly typed vs strongly typed language is that there is not really a rigorous technical definition for either term; therefore, using such terms should be avoided altogether. However, I include them here for the sake of contrasting the common usage of these terms with that of dynamic/static typing.

A strongly typed language is generally regarded as a language in which variables must be explicitly converted when comparing two variables of a different type. In other words 1 + “1” is an impossible operation and 1==”1″ is an impossible comparison. In general, in a strongly typed language both of these statements would return an error. In a strongly typed language variables of different types must be explicitly converted in order to perform operations and/or comparisons.

A weakly typed language, on the other hand, will usually implicitly convert variables of different types when the programmer attempts a comparison or operation on them. Usually weakly typed languages have elaborate rules (though often consistent with many other weakly typed languages) stating how conversions occur implicitly (or automatically, without the programmer needing to explicitly state that a variable is to be converted). For example, the operation 1.0 + 1 will likely return a float value in a weakly typed language, whereas it would likely return an error in a language that is considered to be strongly typed. This is because technically this statement is comparing a floating point number with an integer.

Dynamically Strong and Statically Weak

It is also a misconception that a dynamically typed language must also be weakly typed and that a statically typed language must be strongly typed. This is also untrue. Python is a good counterexample to the first scenario. Python is a dynamically typed language; however, it is also generally considered to be strongly typed. On the other hand, while both C and C++ are statically typed, they are also considered to be weakly typed; that is, there are many ways in which variable conversions occur where the programmer does not have to explicitly direct the program to make such a conversion (1.0/1, for example, will return a floating point number).

Dynamically Compiled and Statically Interpreted

Finally, I should also note that, while dynamically typed languages are generally interpreted and statically typed languages are generally compiled, this is certainly not a requirement either. Many dynamically typed languages can be compiled, for example, Python and Ruby. However, when these languages are compiled they are still not typically as efficient as their statically typed counterparts due to the extra overhead of runtime type checking.

While knowing the differences in these terms will not make or break you as a programmer, it can be useful to understand the differences so you properly understand what is going on “under the hood” with your language. It is also important to understand these definitions so you can properly understand and possibly even partake in discussions with your peers and coworkers and speak intelligently about such things to those around you.

Dynamic Typing Vs Static Typing Vs Weak Typing Vs Strong Typing