May 18, 2021

Getting brave with Dapr

A standardized library available to help developers in things like look-ups, persistence, security or messaging, has been a goal for platform creators since before even J2EE was first released. Today, we look at the latest incarnation of this goal. It’s called DAPR, and it’s made by Microsoft. Let’s go!

Oh no, he didn’t

Did he compare the latest hippest, fanciest, shiny technology to hit the market with the old-aged brontosaurus that is Java Enterprise? Have the oily vapors of his excessive Friday french frying dinners have finally gone to his head? I hear you think it - but before you ctrl/cmd-f4 this tab to read something else, allow me to explain my point of view.

I mean, if you look at the abbreviation of Dapr - Distributed Application Runtime and wanted to apply that to a piece of software 20 years ago, you’d be hard-pressed NOT to point at an J2EE application server.

Now, I am not talking about the implementation yet. I am merely stating the goals of either runtime. Offer a set of APIs to developers to build distributed applications, and handle earlier mentioned boring stuff like scaling, look-ups, state management. Both define a containerization format for packaging and shipping applications. In the old days, J(2)EE depended on application servers like Websphere or Weblogic. Nowadays, DAPR builds on the more modern container technology - Kubernetes.

Enterprise Java depends on A LOT of specification and documentation. DAPR is a lot more lightweight and thankfully composed of all the cloud-native tools and practices that have become familiar to us over the last five years or so.

Staying clear of the mesh

So, in 2021, when you consider some of the earlier-mentioned aspects, service registration & look-ups, security, logging, and the likes, it would be easy to think that this is another service mesh like Istio or Linkerd. These utilize sidecars that run next to your application pods and do a lot of that background work. As explained on the official Dapr site, that’s not a fair comparison, however.

Long story short; Service meshes like Istio or Linkerd usually operate more on a networking level. Dapr works a bit on that same level but also very much provides ‘higher’ services.

Even simpler said - and very much my opinion - as a developer, you are not likely to code very much to, for example, an Istio API. Your platform team/provider will probably set Istio up, you provide some annotations in your deployment scripts, and bob’s your uncle. Not so much with Dapr - which does have developer-facing APIs, and so, again, is quite like that old coffee slurping dinosaur I shall not name again.

Of course, thanks to the flexibility of Kubernetes, you can have your cake and eat it too. You can run Dapr and a service mesh on your cluster and have the best of both worlds. Maybe I’ll get around to figuring that out someday, but not today.

Dapr is not a service mesh, though there is some overlap. It should work fine with existing meshes, though you’ll need to pick which (the mesh or Dapr) does which parts of the work.

Developers, developers, developers

Now, how does all this magic work in Kubernetes? After all, if you package your software in a container, that’s it. There’s no extra magic. To add this functionality, Dapr uses the sidecar pattern; I can best explain it using the following complicated architectural diagram:

Dommel or Wowser? you be the judge!

All of the service-meshes also use this pattern to do their magic, and it’s common in Kubernetes land to do things like this. Of course, as a developer, using just a service mesh, in theory, you don’t need Kubernetes to do some business logic development. The added benefits of a service mesh really come into play later down the ‘software delivery road’ in an acceptance or production environment.

But with Dapr, this does not apply; since we will be programming against the Dapr API as well, even our early development environment needs some instance of Dapr car to do the stuff the Dapr API promises us it will do. Now, one could imagine things getting a bit hairy - do we need a Kubernetes cluster to develop a micro service?

I mean, I love Minikube as much as the next guy but still. Say I am developing 500kb of Golang binary. For me, getting Kubernetes in any form, complete with the YAML configuration and so on, just for some quick development and testing work, kind of defies the purpose. Like; 500 kb of executable code and two megabytes of YAML configuration? No, thank you.

Luckily for us, this potentially disastrous developer experience was handily avoided by the Dapr team. Developer workflow in Dapr works with a CLI on your local machine/. This Dapr CLI can use a local Docker installation to spin up the sidecar. But your actual microservice doesn’t have to run in Docker; rather, the local Dapr CLI can ‘bind’ the sidecar container to a simple local process like so:

dapr run --dapr-http-port 3500 \
--components-path components \
--app-id myapp \
--app-port 8080 \
mvn clean compile exec:java

There’s a lot to unpack here, and really, it would be easiest to start from the bottom up. This little command above ultimately starts a little java program. The last ‘command’ in the pipeline is a simple mvn clean compile exec:java statement. As you can expect, this is what compiles and runs your code. It can be a dotnet run or a go run or - if you’re not afraid of a little masochism - something like a node or npm command. Hey, I’m not judging - you do you.

The second to last line is the argument --app-port 8080 - this tells the Dapr CLI that process running is going to be exposing something on port 8080. This port is where the sidecar will Thisforward the incoming requests. Likewise, -app-id myapp is like an identifier for an application used locally. It will be important for invoking services on this app via Dapr.

This blog is already becoming quite long - so I won’t go into too many details about --components-path yet; but to sum it up quickly, you need to specify a folder somewhere that contains definitions, if any, of components like a state store, pub/sub, and so on. If you’re still thinking j2ee, and you think of the components folder as a declaration of services you want to be loaded into the jndi of your app server, you wouldn’t that be far off. Kind of like all those XML files back in the day. But without the terrible IDE ui’s and wizards for filling them out. If I ever see a wizard or UI for filling out my component YAML files, I’ll be mildly disappointed.

As you can guess, the last/first line is a simple dapr run command, followed by the http port we want to use to access our Dapr service something like http get http://localhost:3500/v1.0/invoke/myapp/method/hello This is a simple HTTPie command (I prefer it over curl) to do a get operation. A quick dissection learns that /v1.0/invoke means you are going to invoke something (via version 1.0 of the Dapr API) on the following app-id, being myapp. We’ll call a method called hello. The sidecar forwards the HTTP get request to localhost:8080/hello and returns the result from that call. Of course, there is a bunch of other things happening as we do this. Let’s dig deeper.

Sidecar shenanigans

From the above example, we can establish that there is some API gatewaying already going on. You can invoke methods in Dapr using either plain old HTTP calls or using GRPC (which is protocol buffers over HTTP2); The Dapr runtime translates any invoke request to a GRPC payload between the various middlewares active inside the Dapr environment. Between these middlewares, Dapr does things like tracing or authentication before translating the invoke request to the protocol used by the actual service and calling that. The response is again wrapped, pushed back through a pipeline, and ultimately translated to the format used by the caller.

Eternal Interface Issues

This is all fine and well, but it does mean as a developer, we have some weird scenarios that could happen if you look at the invoke interface. I could create an HTTP service, listening to a path something like /books/createNewBook. Then, I could post an HTTP GET request with the book data and even have the service do the thing. It goes against all things REST and is the kind of offense that would get you a stern talk if I’m reviewing the code. Remember that drill sergeant from Full Metal Jacket? Exactly

Just another code review on faulty REST interfaces

You can always do this using any language or service framework - God knows we engineers are very good at finding ways to take a perfectly good technology and mess it up - but, using Dapr; the new URL almost makes more sense if using HTTP to call it; /v1.0/invoke/bookapp/method/books/createNewBook. Of course, you still have to specify the correct HTTP method. So, I’m not sure how to put it, but for me, it just feels off to use HTTP to do this RPC. Not so much because I hate the technology, but because, as a software developer, I suddenly feel it’s feasible to pick a REST-like microservice framework and try to force it to do RPC. Which just feels icky.

Of course, the easier way for this procedure call scenario would be to create a GRPC service. This works and is something I want to write about, but the biggest issue here is that your own GRPC service has to implement the actual GRPC interface contract of the DAPR invoke API. It makes a lot of sense if you know anything about protocol buffers. But if I write an HTTP server, I am left very free to muck up in any way I can. But, if I use GRPC, I need to abide by the straitjacket that is the invoke API.

Now, call me kinky, but I will happily pick the straitjacket for RPC scenarios. Still, this is the kind of liberty for a developer that does worry me, as it’s so very tempting to forget what type of interface we’re implementing in our service if you just use HTTP in your microservice. I understand there’s little choice - cause if you want to support REST-styled interfaces, you’re saying to people: ‘Hey, here’s HTTP. Do your worst’. But still, it did leave me with a bit of an odd taste, if only because I had to learn GRPC unexpectedly.

Wrapping up

So, I’m about 2000 words in. I’ve covered the bare basics of the Dapr framework/platform/but-not-mesh. What do I think of it? Well, at the very least, it’s refreshing. It’s a framework that provides developers an actual API, much like any microservice framework would. But also, it’s a sidecar and a combination of tools and components that knit into it.

Therein lies a risk of coupling, but when setting up Dapr in an actual Kubernetes cluster, you can still pick which components you want to fulfill what role. You can do a state store in SQL or, by default, in Redis. Want to do Pub/Sub? Take a pick; RabbitMQ, Kafka, Redis(Streams) are among the few supported options. So, there is flexibility and choices to make, which is a good thing. If you are using a cloud platform, there are probably various component implementations that use that cloud’s specific services (for example, Azure Service Bus or AWS SQS for pub/sub).

But, as one will find out if, either looking deeper or just by, well, reasoning logically, there is a price to pay. Because while you can pick your choice of implementation for any of the building blocks, you can’t, at least not via the Dapr APIs, actually use the product API of your choice that you might know and love. That, of course, makes sense, but it does take away some of the goodness of, for example, Kafka versus RabbitMQ from a developer point of view.

In the end, Dapr is attempting an ambitious crossover between the world of Kubernetes/infra and developer APIs. In doing so, it has to make choices. Because it’s doing more, it’s not as good as any specialist component used for that bit. So, from a perfectionist view, it’s never going to measure up in flexibility or speed. But then again, it does what needs to do well, and it hopes to be applicable for most application needs out there.

One can (and I hope we will) debate whether or not it succeeds at that. But so far, I’m just happy there’s a new piece of developer technology out there that is attempting to alleviate the ‘middleware burden’ - instead of adding to it and help engineers focus on implementing some actual business value.

I’m working on a bit of a hobby project with Dapr, and it might give me some more input to write about in the coming months. I’ll hopefully be back soon with a deeper dive into State management, the freaky world of key/value data stores, and that slightly awkward feeling when I have my own opinions come back to haunt me.

Corstijan Kortsmit 2021

Powered by Hugo & Kiss.