Building a gRPC Service with Ballerina — Part I

Kevin Hoffman
7 min readJul 24, 2018
Ballerina Language Logo

Before I get into Ballerina, I want to explain the perspective from which I’m viewing this exploration. Lately, I’ve been suffering something that I can only describe as service fatigue. I’ve been suffering it so much that I’m contemplating writing another post on this ailment and how we can avoid it. The tl;dr of service fatigue is the exhaustion we feel after building service after service after service and repeating so much of what we do, either in terms of boilerplate or copy/paste or process, and the actual business logic is such a tiny portion of what many of us do. Service fatigue is like a repetitive motion injury for cloud native app developers.

And so one morning I was casually perusing Twitter, looking for someone to post a juicy bit of coding goodness, some morsel or scrap of tech knowledge that I can lap up that will tide me over until the next revelation. This is where Ballerina comes in.

The language seemed to have primitives for concepts like services and endpoints. It looked like most of the boilerplate I was so sick of writing had been codified directly in the language as first-class citizens. So, naturally, I dismissed it. Wait.. I did what now?

Part of what I do is figure out which new things will have an impact on me, my team, my company, or my side projects outside of work. I’ve seen gimmicky languages before and this felt like one with its sequence diagram visualization, so I went back to learning stuff from more mature ecosystems and, to be clear, Ballerina is very, very young.

Compared to Ballerina, the Rust ecosystem began in the age of the American colonies and the Go ecosystem began during the Bronze Age. So for a while I ignored it and went back to looking for other new things to learn.

Then I saw a blog post that caught my eye and I started reading. Ballerina wasn’t just a set of libraries to do common cloud native things. It has a deeply ingrained lightweight threading system (e.g. coroutines/goroutines) that is used everywhere (apparently every function has a default worker, though you don’t see it in the code). The sequence diagrams are aware of parallelism and weren’t designed to be shown to non-technical people, they were designed to help facilitate communication among service developers. It brands itself as a cloud native integration language.

This got me thinking about my service fatigue. In addition to boilerplate, so much of what I do is integration. My services talk to databases or they talk to other services or they offer themselves up to be spoken to by other services, and they have to do this asynchronously with security and reliability and fault tolerance, etc. The more I read about Ballerina, the more it seemed like this language was actually trying to solve that problem.

Could someone be building a language designed to alleviate service fatigue? Well, now I had to try it out and see for myself, and not just with random “hello world” samples.

I dove into the documentation and the samples and actually found it difficult to navigate. Some of the guides assumed too much previous knowledge, other guides would illustrate how to do something so simple (like send/receive a simple string) that it didn’t give me any production-grade context. What does this code look like in a real app?

With help from some Ballerina folks on Twitter, I got past a few hurdles and a bug in the compiler and was able to build a gRPC service. Here’s the first bit of it, where I’m defining an endpoint and a data type that will eventually become a message in a protobuf IDL.

So far this looks a little familiar. I’ve seen keywords like endpoint in other language’s libraries. Spring Boot will let you annotate stuff at this level of abstraction. I’ve seen Ballerina samples where the values in the endpoint config come from environment variables, so that’s promising in terms of being cloud-friendly.

The map bugs me a little. What’s the key type? In Rust, I’d define Map<String, DroneInfo> so this threw me a bit. The key here is a string. I’m not sure how I’d use a non-string keyed map, but I’ll put that on my to-research list for later. I’ll be replacing that when I use a real database, so it’s low priority for me.

Next, record seems to be a pretty straight-up match for a struct , so that’s an easy concept to grasp. Now let’s take a look at the rest of the code:

On the surface, this looks like a DSL that you could produce in any other kind of language to rid yourself of some redundant library calls to set up your service. The interesting thing here, however, is that this isn’t code generation, nor is it macros wrapping an underlying library. This is part of Ballerina’s standard library and service is a language-level keyword with a very specific meaning (something that is bound to an endpoint to send/receive network data).

The next thing I found interesting here was that this code doesn’t hide the network. This is a pet peeve of mine, but I firmly believe that a number of really horrible pieces of software have been built because that software was built on top of network-hiding abstractions. If you hide the network, then you need to hide retries and timeouts and short circuits and even the simple idea that a send might fail. So I’m actually pleased to see that these functions don’t have return values that are then converted into network messages.

The but syntax got me hung up initially. This is one means by which Ballerina deals with optional types (indicated with a ? after the type name). Map access might fail, so the indexing operation returns an optional, so…

dronesMap[droneId] but { () => {} }

…actually returns the real value, but in the absence of a value ( e.g. ()), it returns what I think is an empty record {} via pattern matching. I haven’t quite figured out Ballerina pattern matching, except to notice that I can’t declare new data and destructure at the same time — I have to declare variables and then destructure into them. Rust lets me destructure directly into variables that are then immediately available within that lexical scope, so it makes for smoother code.

This looks like some pretty terse code, but it’s still “hello world”-y, so I’m planning on adding more realistic stuff to it in the next blog post. I built this code by running ballerina build in the directory where the .bal file is.

This created target/grpc/DroneMgmt.proto automatically for me. From this protobuf file, I can generate a Ballerina stub and an empty client application. Here’s the protobuf file generated from the preceding code:

The idea of going from code to protobuf first feels a little scary to me. Just to see what would happen, I went back into my Ballerina file and added a few fields above the battery_remaining field.

I was dismayed to see that the newly generated protobuf IDL had renumbered the protobuf fields, which will break running services built on the previous version of the protobuf file. So I commented on a GitHub issue about this and hopefully it’ll be fixed with field-level annotations in the future.

So what does a Ballerina client look like for a gRPC service?

This is a very simple, very terse, pretty easy to read set of code. You can see here what I was complaining about, how it looks like I have to match on the pattern and then extract the data from the pattern rather than doing both at the same time. I’m assuming I’m just too new to Ballerina to know better, so I’m hoping there’s a cleaner syntax for this.

So, this is all well and good, but Ballerina also promises to improve our lives after day 1. It may help with what I call builderplate — the copy/paste repetitive stuff we do to get our apps to production.

Ballerina promises to make operations and deployment easier as well with built-in support for monitoring, observability, failover, short-circuiting, security, docker and kubernetes. So, in upcoming blog posts I’ll be taking this sample from this little service to a fully functioning service that I hope to have auto-deploying on a Kubernetes cluster.

In the next blog post, I’ll try and refactor some of this code to make it even simpler and easier to read while I add support for a real database.

There’s one more thing. What about that visualization “gimmick” that I mentioned earlier? I am the first one to admit when I’m wrong, and I was wrong. This isn’t just a gimmick. Take a look at the sequence diagram for the client:

gRPC Client Sequence Diagram

I was pretty impressed when I saw that. But, I may have snorted a little coffee when I looked at the sequence diagram (generated right inside VSCode!) for the server:

gRPC Server Sequence Diagram

I am definitely intrigued enough to keep going with this experiment, and so I will definitely try and hook this service up to a real database and I’ll post again my thoughts on the experience and what I’ve learned.

Check out the next article in this series, Part II — Integration.

--

--

Kevin Hoffman

In relentless pursuit of elegant simplicity. Tinkerer, writer of tech, fantasy, and sci-fi. Converting napkin drawings into code for @CapitalOne