I never minded these frameworks, but then I wanted to write a few parsers for some file formats and they used Thrift/Flatbuffers to encode a few ints, which seemed like a major overkill. There was no RPC, no packet loss, no nothing.
I never minded these frameworks, but then I wanted to write a few parsers for some file formats and they used Thrift/Flatbuffers to encode a few ints, which seemed like a major overkill. There was no RPC, no packet loss, no nothing.
True, in many use cases, performance is not critical. However, when you're doing big data processing or serving at scale, serialization performance does in fact start to matter a lot. If Google Search passed all backend messages as JSON rather than Protobuf, it would probably require 10x the hardware (we may be talking about millions of machines here) and wouldn't be able to respond as quickly. Similarly, Cloudflare couldn't handle its logging load without Cap'n Proto.
Many times, companies who started small and thought performance didn't matter later found themselves needing to do massive, expensive rewrites in order to keep up with scale (e.g., famously, Twitter).
Hand-written binary serializers may be easy to write initially, but they are very hard to maintain. Over time, you'll need to add and remove fields, while maintaining compatibility with old binaries already running in the wild, or compatibility across languages. It's very easy to screw this up with a hand-written encoder, but very easy to get right with Protobuf or Cap'n Proto.
Finally, even if performance isn't an issue, you may find that type safety is useful. If you are writing code in a type-safe language, correctly dealing with JSON actually tends to be a huge pain involving lots of branching. By defining a schema upfront and generating code, you can get a much nicer interface. And if you're doing that anyway, then you might as well take advantage of the more-efficient encoding while you're at it -- it's easy to dump human-readable text for debugging when needed. (As of this release, Cap'n Proto ships with a library for converting to/from JSON, and there are lots of third-party libraries to do the same with Protobuf. Both libraries also have one-liner "dump a debug string" functions.)
I mean that when I write code to consume a message, I would like to detect (at compile time) if I typo a field name, and I also don't want to worry about what happens if a (possibly-malicious) sender sending me a different type than I expected.
I think binding is precisely what is going on when a compiler generates code for marshaling and unmarshaling messages based on a schema.
I'm merely pointing out that the term is vague and can cause confusion. That you make a strong association with one use of the term doesn't really govern what others associate with it.
Recent releases have it built in already, just FYI.
(Also, hi Kenton! I spent lots of time looking at your code when hacking on protobuf :))
Here's one example that has repeated itself a few times in a few companies. I kinda like having logging that works without strangling what you are monitoring and/or the server doing the monitoring. I also like having extensible log servers that you can write plugins for (although I'm not that fond of the idea anymore as people invariably will write _slow_ plugins, and then you are screwed. Do put in the APIs, but don't tell anyone it is easy to add processing chains). I like it to the point where people made fun of me for this "obsession" even 15 years ago.
So it goes like this: I use some fast serialization that can be parsed using next to no CPU per message -- or in later years, using Protobuf or similar. Because it is more convenient and I can throw in binary payloads in exceptional cases. I benchmark and I usually get high ridiculously high throughput without even making much of an effort. Great, this saves me the trouble of having lots of log server horsepower and I can get on with life. (Stuff will need to be sharded eventually, but since we keep everything dead simple and we actually have a plan for how to do this when needed I don't worry).
Then at some point someone who has never had to write fast code comes along. It used to be "let's use XML". Now it is invariably "let's use JSON". Because "it is fast enough". This is where it starts. And they'll use Ruby, or something else that is godawfully useless for anything that is supposed to be high throughput, because hey, it is what they understand.
And for a while, it is fast enough. Sure, you now have only a fraction of the original throughput, but the systems are lightly loaded and you are not bumping into any CPU limits. "See!? I told you it would work!".
Then people start to do serious logging and the stuff can't keep up anymore. So they start coming up with lots of schemes for dealing with the load. For every scheme implemented, stuff gets a bit more brittle and complex because now the code has all of these assumptions and mechanisms to preserve. And before you know it, you have a slow, complex logging system, that everyone depends on and nobody wants to touch. Well, sometimes someone will reimplement it, but they'll use whatever is hip at the moment. Like JavaScript and then ooh'ing and aah'ing when they double the throughput -- even though they kind of originally lost 2-3 orders of magnitude of performance.
And it isn't like making a performant version is a big task. With Cap'n Proto, Protobuffers and whatnot, someone has done the heavy lifting for you. Someone a lot smarter than you. And it is probably going to be faster to implement, more efficient, and easier to maintain in the long run.
Performance matters where it matters. And it _is_ stupid to forego it when it costs you little or no effort. And yes, being an old fart means I don't give a shit about people's egos anymore and I'll call them stupid to their faces when they are being stupid.
If you log using JSON (or XML, yech!) as your envelope format, you _are_ stupid.
Others reasons are:
- Starting from an IDL you get a nice service contract. Which means both sides of the service (client & server implementors) get a strong description on how the service should behave and what they need to implement. The more roles you have (architects, client-implementors, service-implementors, testers, mock-service implementors), the more you profit from a good specification.
- If you generate all the serialization/deserialization code you avoid the tedious handwriting of this code and the possibility of introducing errors in this. E.g. some misspelling of field names or functions or copy&paste errors can always occur and some of these errors are only detected late in the development process.
These advantages might not be visible if you have a small service and development team - you might even experience an overhead compared to just "changing the client and server code", which are both under your control. But once you are working on a system with thousands of APIs you are quite happy if you don't have to care about handwriting serialization and RPC code and instead can focus on business logic. I personally was responsible for deploying a similar solution for an in-vehicle infotainment system with hundreds of services and around 4000 functions, which worked out really well.
Out of curiosity, what format did your infotainment system for serialization?
I'll be surprised if you use a language that has a good protobuf implementation that doesn't also have a good json/yaml/xml implementation.
Incidentally, as far as I can tell, protobuf also neglects performance. For high performance you want as much of your message to be predictably positioned in the bytestream as possible, but protobuf uses a key-value style system which means you always have to look at each key-value pair to determine what it is and how big it is. This makes good sense for compatibility, but it's a definite trade off that limits how fast deserialization can be.
I can't speak to XML, but can say that even with "type safe" json serialization, I've seen several bugs due to some json libraries treating all numbers as doubles internally, meaning bad things happen when you're actually serializing integers larger than 52 bits (say, a nano timestamp). Sure, maybe it's a bug in the json library because the json spec doesn't establish any max precision for numbers, but it's not hard to run into it when different components/languages with independently implemented json libraries try to talk to each other.
Shared schema approaches like proto also make it really hard to accidentally typo the name of the field, or accidentally try to read a field as a string rather than a list of strings.
Just like I wouldn't consider code to be type safe if it passed data around as raw Objects and each usage cast it to the expected type, I wouldn't consider services that communicate with each other using independently (manually) implemented parsers/extractors as being type safe, even if the "meat" of the code is dealing with strongly typed data on either end.
I don't see any reference to range/precision in the spec, other than the suggestion that numbers that can be represented as an IEEE double are likely to be interoperable, and integers within 53 bits of range will generally be interoperable due to exact agreement on their value.
https://golang.org/pkg/encoding/json/#Unmarshal
They have some reasonable examples there.
Mapping arbitrary JSON to Go types may or may not be simple, depending on the data format.
Personally I think it's easier to maintain a format defined in an IDL like Protobuf or Thrift than a JSON format. Everyone is always on about how JSON is a "human-readable" format, which is nice and probably 90% true. What we want is a human-writable format, which neither JSON nor YAML really are. It's easier to define a structure in Thrift than in JSON.
I still gotta review this release of Cap'n Proto, but after struggling with some buggy language implementations from the upstream Thrift project and evaluating Protobuf 3.0 + grpc, I think that Protobuf 3.0 is going to be the way forward for now.
If performance ever becomes an issue (unlikely in my case) I could probably switch to some binary serialization protocol eventually.
But I'd hazard a guess that you're spot on regards the normal needs of normal programs. The vast majority of programs utilizing message-passing are not serialization-bound. For these programs I'd recommend JSON because it is easy for humans to inspect and debug, and there are plenty of libraries to choose from if you are not performance-sensitive.
(YAML is hopeless regards security and XML is just horrid to look at when you're debugging).
But I can certainly understand where you are coming from. I've designed several text based protocols in my time for that exact reason. And I still do for stuff that I positively know will never require any sort of performance (mostly on embedded systems, somewhat ironically since those are sometimes constrained down to just a few hundred bytes of memory :-))
For my use case in electronic trading, a format like this is great b/c you don't waste many CPU cycles in decoding/encoding while at the same time you can take complex message structures and easily generate code stubs to read/write them in various languages. You change something? Just regenerate the code stub. You can even guarantee backwards compatibility if needed.
I'd also disagree that performance on message passing doesn't matter; depends how many messages you're passing :) I've seen CPU-bound systems which spend most of their CPU time decoding JSON.
To me, formats like JSON or XML have one clear advantage: debugging is easier. I can fire up a decoder for a binary format, but that slows down the investigation.
That said, most binary formats have a schema file due to necessity of not serializing field names. That schema file makes it easy to have tools that spit out language bindings. And that reduces the tedium of actually writing code that uses (and validates) the data, so I generally prefer those formats for that reason.