Hecklers in Development
Code, coffee, & camaraderie. Collection, unordered. ;)
As you begin to divide conglomerate functionality into discrete, decoupled microservices, you introduce a number of opportunities and challenges into your system(s). The opportunities are often well-known, including (and especially!) development velocity, fidelity/fit (functionality matches requirements), scalability, and a host of other “ilities”.

 

Challenges also exist of course, including the question of how to gain visibility into end-to-end interactions involving multiple microservices across process and network boundaries. Spring Cloud Sleuth provides a lightweight, configurable, and easy way to begin capturing trace information within your distributed system.

 

Distributed tracing: a (very) high-level overview

 

In 2010, Google published their seminal paper titled Dapper, a Large-Scale Distributed Systems Tracing Infrastructure.

 

In 2012, Twitter created Zipkin during their first Hack Week, based upon the Dapper paper.

 

Briefly, an entire end-to-end interaction that completes a request cycle (regardless of transport/mechanism) is referred to as a “trace”, and each trace consists of multiple “spans” connecting the endpoints of each hop. From the Spring Cloud Sleuth project page:

A Span is the basic unit of work. For example, sending an RPC is a new span, as is sending a response to an RPC. Span’s are identified by a unique 64-bit ID for the span and another 64-bit ID for the trace the span is a part of. Spans also have other data, such as descriptions, key-value annotations, the ID of the span that caused them, and process ID’s (normally IP address). Spans are started and stopped, and they keep track of their timing information. Once you create a span, you must stop it at some point in the future. A set of spans forming a tree-like structure called a Trace. For example, if you are running a distributed big-data store, a trace might be formed by a put request.

Sometimes within the Spring space, you hear about Sleuth and Zipkin within the same discussion, often within the same breath…which understandably can result in a bit of confusion on the listener’s part. Without diving too far down the rabbit hole, Sleuth provides the means to instrument your Spring Boot/Spring Cloud applications; Zipkin can take that data and provide a means to monitor and evaluate it. Zipkin provides numerous integrations, but of course, you can also use other log monitoring & management tools to collect and analyze that vital data.

 

Minimum Viable Product

 

This post focuses upon Sleuth and provides a quick on-ramp to getting started capturing basic information for each span. To do that, I’ve created these two projects that allow us to quickly add trace & span (& more) information to our interactions and verify it in our logs.

 

Creating a provider service

Starting at the Spring Initializr, I added the following dependencies to a project and named the artifact sz-provider-service:

(click/tap to enlarge)

 

After generating the project and downloading, extracting, and opening it in our IDE, we see the selected dependencies in our Maven pom:

 

 

Or if using Gradle, build.gradle:

 

 

To provide better context within our logs for our tracing data, we’ll add the following entry to our application.properties file:
spring.application.name=sz-provider-service

 

For this example, we’ll create a very simple RestController so we have something in our provider microservice to contact from a consumer service. Here is the entirety of the relevant code:

 

 

The only thing of particular note is the @Log statement. Lombok provides this capability to reduce the usual boilerplate code required to get a logger via LogFactory.

 

Creating a consumer service

For the purpose of this introduction, little changes between provider and consumer project configuration & code. Here are the exceptions:
  • Revisiting the Spring Initializr, we change the artifact name to sz-consumer-service, keeping the same dependencies
  • We add the following entries to our consumer microservice’s application.properties file:
spring.application.name=sz-consumer-service
server.port=8081

 

NOTE: We’re running the provider on port 8080 (Tomcat’s default port), so we change our consumer service’s port to 8081 to avoid conflict.

 

  • This is the code for our consumer:

 

 

The same note about Lombok’s @Log applies here as well, of course.

 

The results

 

Running the two applications, we can now access the consumer’s endpoint from httpie, cURL, or a browser:

 

 

Doing so results in the following log entries from our consumer service:

 

 

And our provider service:

 

 

Examining our log entries for both services (above), we see that Spring Cloud Sleuth has placed our spring.application.name (as designated in each service’s application.properties), the appropriate trace id, and the span id specific to this hop within the logged information.

 

Summary

 

Of course, Sleuth captures much more data and allows for extensive customization (including enabling additional elements you specify to be captured, adjusting sampling rates, & more), and Zipkin remains a topic for another day. But this post should have provided a quick springboard (!) into using Spring Cloud Sleuth for better insight into your microservices-based system of systems.

 

Happy Sleuthing!

 

Like this post? Follow me on Twitter for more! And be sure to check out the Related Posts below.

 

Additional information

 

Share

Related Posts:


Tags: , , , , , , , , , , ,

Intro

I started this post a couple months ago, and between a busy holiday season and constant revisions, I’m only now polishing and posting it. For those who knew it was in the pipeline, thank you for your patience. 🙂

I have another article in the works that will delve into more technical aspects of the microservices discussion; hopefully I’ll get that posted by this time next year (!). In this one, though, I’d like to stay as much as possible at the conceptual level, dipping into the details only to reference them for consideration and future discussion. No guarantees, but that’s the plan. 🙂

Discussion

First, a good-natured poke at our discipline in general…

The Holy Grail

The Holy Grail of Software Development

Regardless of the new architecture, technique, methodology, framework, language/feature, etc., we (as developers) often fall prey to the temptation to make it a “one size fits all” solution. Regardless of the problem at hand, we break out our new toy, er, tool and apply it vigorously, without the benefit of objective consideration of suitability to the task at hand. To paraphrase two Abrahams (Kaplan and Maslow) the way I’ve always heard it:

“To the man with a hammer, everything looks like a nail.”

This doesn’t have anything to do with microservices directly…only with the fawning attitude for the concept that seems to be prevalent at the moment. 🙂

Along those same lines, I’d like to issue a couple of disclaimers here. I work for Oracle, so my experiences and worldview may differ from yours. That said, I like to think of myself as a fairly fact-based being; while I have been wrong many times and shall be again (many times), I do my best to analyze and come to conclusions based upon objective measures and reasoned assessments. Really, I do.

Secondly, it is incredibly difficult to make statements about microservices or anything else within the dev sphere that are 100% accurate and/or conclusive. I’ve tried to avoid blanket generalizations, but frankly, it’s nearly impossible to discuss anything without making some sweeping statements that one assumes/hopes will be understood as they were intended. If something below puzzles you or doesn’t sound right, please drop me a line and ask.

And finally, the views expressed in this post are mine and don’t represent those of Oracle. Or maybe anyone else. They’re just mine. 🙂

Now, back to microservices!

As with any tool applied indiscriminately to all problems, fit is often imperfect. Let’s take a closer look at what microservices are, what they’re up against, and their particular strengths and weaknesses.

Definitions

Microservices

“…the microservice architectural style…is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.”
Martin Fowler

Let’s dissect this a bit.

“…the microservice architectural style…is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API.”

This is a good start. Without going overboard trying to explain something that is fairly straightforward, we’re talking about breaking down a large, complex application into small(ish – we’ll get to that a bit later) services that are more granular. Doing so should enable us to focus upon a more specific outcome for each portion of the overall functionality, which should result in better component manageability and quality.

“These services are built around business capabilities and independently deployable by fully automated deployment machinery.”

With all respect to Mr. Fowler, this bit of text is not specific to microservices. Capability-driven development approaches are used in many different contexts, and automated deployment is even less useful as a differentiator. For this discussion, we’ll ignore this bit of text entirely.

“There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.”

Unlike the previous statement, this is a very useful description of microservices that, while not entirely unique to them, lays out two key tenets:

  • Microservices are decentralized. They could be hosted anywhere.
  • Microservices are heterogeneous. They can be written in the developers’ language of choice and deployed on their platform of choice, as long as the “connections” are properly made.

The “Alternative(s)”

Everything else, of course. 🙂

The microservices concept primarily addresses the server side of the equation; the client component is a player only insofar as it is involved in the delivery of the application’s functionality. In the strictest sense, microservices focus upon the delivery of capabilities via (micro)services provided by server(s).

Keeping that same specific focus, the alternative is really the “monolithic” application, typically delivered as a single unit containing all functionality for the entire set of targeted use cases. Some means of delivering so-called monolithic applications include (but are not limited to) Java EE, .NET, or any other server-side environment that provides functionality to the user via an application server or an affiliated cluster of app servers.

Pros of a Microservices Architecture

Manageability

Firstly, anyone who has struggled under the weight of a large server-based application can immediately see the appeal of microservices. Monolithic apps can get “too big”, becoming unwieldy to develop and maintain. With so much complexity in a single code base, it becomes difficult to test, debug, etc. without the law of unintended consequences wreaking havoc in novel ways.

Extensibility

With a microservices architecture, functionality can be “farmed out” and provided by others, often developers/teams who don’t even work for your organization. While this isn’t unique to microservices, the architecture certainly facilitates it; creating and documenting the interfaces within the core app to support various connection points makes developing and integrating external functionality far easier for those outside the core application’s dev team than any monolithic app could be.

Location/Scalability

This also allows hosted functionality to be geographically dispersed more readily than typical alternatives, deploying microservices wherever it makes sense to do so (using any criteria). If a development partner on one side of the world decided to provide a microservice that integrates with a core application hosted on the other, this could be done easily by complying with published interface standards and requirements. Adding an external functionality provider is far less disruptive to the core app if it was designed with that end in mind, and that is the core principle of a microservice architecture (see above).

Granularity

Creating an application with microservices in mind also can result in a leaner core application. It’s an imperfect analogy, but composition vs. inheritance applies here. If your architecture embraces the creation of a core set of functionality with numerous extension points, your core app potentially can be more focused and functionally cohesive…as can each microservice.

Cons of a Microservices Architecture

Any architectural decision has its downside, and a microservices architecture creates new challenges while it’s solving old ones. Here are a few potential hurdles that microservices face.

Control

If you thought debugging and testing a monolithic application was a challenge – with all code directly accessible and modifiable (to varying extents) from the comfort of your IDE – you’re probably cringing at the thought of your pending inability to sink your hands into the entire codebase to fix bugs that surface or (more) easily test and diagnose aberrant behavior that is reported in your app. End-users will not know (and won’t care) that something breaks due to a partner’s error in implementing an interface, failure to completely debug an edge case, or even an innovative misuse of the app by said user. Your app is broken, #fail.

Consistency

If portions of your application’s functionality are provided by other groups or organizations, maintaining consistency becomes more difficult. User interfaces, error handling, text phrasing…these are just a few examples of areas where small differences can make a huge impact on the overall user experience.

Overhead

Say what you will about about monolithic apps, but infrastructure and interfaces are costs that are borne once for the entire (monolithic) application…to say nothing of transactions. When portions of your app’s functionality are provided via microservices hosted on various external platforms, connection security and authentication/authorization must be factored into the most insignificant internal interactions. It’s only a bit of an exaggeration to say that with a microservices architecture, there are no local interfaces. And for those who think “microservices” means “tiny services”, there is no limit to the size/scope of a so-called microservice. Factoring in the overhead, they can involve more code than you might expect, somewhat negating some of the advantages of “leanness” you may have been anticipating.

Fragility

With multiple infrastructures and databases potentially hosted on various platforms in far-flung places, a microservices-based application certainly offers more breakpoints. At best, this introduces latency concerns; at worst, fragility. If any key bits of functionality are delivered via an (external) microservice, the unavailability of that microservice breaks your app. This is less of a concern if the group supporting the failed service is just down the hallway; but if the other organization is in a different building, city, or country, small breakages can quickly result in painful outages and even lost customers. See the note about Control above.

In Conclusion (For Now)

A microservices architecture offers another viable approach, another option to deliver more functionality to end-users…but it is not a silver bullet.

A microservices architecture can provide greater flexibility and code quality by capability at the possible expense of (overall) application availability and control. It’s a great tool, used wisely and in the right circumstances…but as with any great tool, it doesn’t remove the responsibility to think prior to (and while) employing it.

Now, go develop something. 🙂

Cheers,
Mark

 

Share

Related Posts:


Tags: , , , , , , , , ,

Powered by Wordpress
Theme © 2005 - 2009 FrederikM.de, heavily modified by Mark Heckler
BlueMod is a modification of the blueblog_DE Theme by Oliver Wunder