Live data from Hacker News

Arguing against using protobuffers

reasonablypolymorphic.com

101–110 of 307 posts

Re: Arguing against using protobuffers

#101

These objections are interesting. I have mixed opinions on protos, I think overall I'm mostly in favor. I'm a bit confused by this set of objections though. While oneof fields cannot be repeated, oneof fields can be arbitrary protos, so they can contain repeated fields. In other words, you can have a (pseudo-proto) oneof { RFoo { repeated Foo; } RBar { repeated Bar; } } so in practice this isn't a restriction. If any…

repeated(oneof(Foo, Bar)) is not the same as oneof(repeated(Foo), repeated(Bar))

Correct, both of those are possible to represent as is though. Each oneof just requires and extra proto to wrap it.

So you have

    repeated OneaofWrapper {
      oneof {
        Foo
        Bar
      }
    }
Or what I did in my previous comment.

Re: Arguing against using protobuffers

#102
post #90

Earlier quoted context omitted.

so, just for fun, here's a way to do that check without double unmarshalling and allocating maps and such for everything under your struct (also does a check for extra fields, but you could pull that out if you want): type Post struct { Title string `json:"title"` } func (v *Post) UnmarshalJSON(b []byte) error { dec := json.NewDecoder(bytes.NewReader(b)) required := map[string]struct{}{ "title": struct{}{}, } tok, er…

Thanks, that's neat! The challenge here is that I also need to validate values (e.g. support minimum/maximum), not just within structs, but also standalone values, which means an UnmarshalJSON directly on the type. I might end up doing something like your example, though. (Rejecting extra fields is on my list!)

the json.NewDecoder stuff does still trigger unmarshals of the types in the struct and such: i.e.

    package main
    
    import (
    	"bytes"
    	"encoding/json"
    	"errors"
    	"fmt"
    )
    
    func main() {
    	fmt.Println("Hello, playground")
    	var p Post
    	err := json.Unmarshal([]byte(`{"title": "test"}`), &p)
    	fmt.Println(err, p)
    	err = json.Unmarshal([]byte(`{"title2": "test"}`), &p)
    	fmt.Println(err, p)
    	err = json.Unmarshal([]byte(`{}`), &p)
    	fmt.Println(err, p)
    	err = json.Unmarshal([]byte(`{"title": "long test"}`), &p)
    	fmt.Println(err, p)
    }
    
    type Post struct {
    	Title ShortTitle `json:"title"`
    }
    
    func (v *Post) UnmarshalJSON(b []byte) error {
    	dec := json.NewDecoder(bytes.NewReader(b))
    	required := map[string]struct{}{
    		"title": struct{}{},
    	}
    	tok, err := dec.Token()
    	if err != nil {
    		return err
    	}
    	if d, ok := tok.(json.Delim); !ok || d != '{' {
    		return errors.New("Expected object")
    	}
    	for {
    		tok, err := dec.Token()
    		if err != nil {
    			return err
    		}
    		if d, ok := tok.(json.Delim); ok && d == '}' {
    			break
    		}
    		switch tok {
    		case "title":
    			delete(required, "title")
    			err := dec.Decode(&v.Title)
    			if err != nil {
    				return err
    			}
    		default:
    			(*v) = Post{}
    			return errors.New(fmt.Sprintf("Unexpected field %s", tok))
    		}
    	}

    	if len(required) > 0 {
    		(*v) = Post{}
    		return errors.New(fmt.Sprintf("Missing %v required fields", len(required)))
    	}
    	return nil
    }
    
    type ShortTitle string
    
    func (st *ShortTitle) UnmarshalJSON(b []byte) error {
    	var tmpst string
    	err := json.Unmarshal(b, &tmpst)
    	if err != nil {
    		return err
    	}
    	if len(tmpst) > 6 {
    		return errors.New("Title too long!")
    	}
    	*st = ShortTitle(tmpst)
    	return nil
    }

Re: Arguing against using protobuffers

#103

Earlier quoted context omitted.

I'd like to humbly suggest that we use JSON please, in particular: JSON + JSONSchema[0] +/- JSON Hyperschema[1] +/- JSON LD[2] It's a bit to learn but I promise you, it's worth it. The technologies are not redundant (jsonschema spec is for validation, hyperschema spec is for specifying how you interact, and LD is for semantics like language and more). If you take a few hours, read all 3 specs, you're almost guarantee…

I don't think GraphQL is over-hyped at all. Maybe it's flawed, but the design is absolutely on the right traack. GraphQL completely changes how you work with APIs in a front end. I work on React apps, and by using GraphQL, a component's data requirements can now be entirely declarative. For example, a component can do this (simplified): {({data, loading, error}) => { return {data.posts.map((title, {creator}) => {titl…

Nothing about what you just posted couldn't be done with a normal RESTful endpoint with sufficient support for column-level filtering and embedded item filtering.

Your post is the perfect example of GraphQL is being over hyped. I absolutely get that there's a benefit to filtering at both these levels, and that the DSL cuts down on noise and gives you a way to "query" without thinking of web requests, but it's not a leap in thinking -- teams that have to deal with mobile environments have long been strapping on filtering options to endpoints to avoid sending unneeded bytes to mobile devices.

Yes -- GraphQL standardizes this stuff, but it does it in a way that is basically not compatible with anything else.

> The component knows what it needs to render, so it declares that.

??? The component doesn't know anything, components don't think. You're describing it as if you just gave the component a list of entities but you actually wrote a query DSL -- that's how the component "knows" -- you told it.

What GraphQL is doing for you here is:

- Enforcing consistent access patterns (which is how "posts" gets translated into the right URL)

- Ensuring "limit" is supported on the endpoint

- Ensuring horizontal filtering is supported

- Ensuring embedded entities get returned and they're filtered

This is not a paradigm shift. It's better, maybe, but that DSL will absolutely fail you at some point, when you try do a more dynamic query, and you'll have to drop back to writing code that looks a lot like life did before GraphQL.

> With TypeScript, you can get type-safety all the way from the backend to the frontend, which means your IDE (e.g. VS Code) can correctly autocomplete, say, "creator." and suggest "name". It's rather magical.

This is basically orthogonal... Write well typed javascript and your IDE is going to be able to help you out.

> Firebase-like data store with schemas and joins

You've lost me here. The excerpt you've posted looks even worse than SQL. At that point why not just send SQL directly to the backend (as long as you can get the permissions right and your DB is secure enough)?

Re: Arguing against using protobuffers

#104
post #60

Hi there, I'm an actual author of Protocol Buffers :) I think Sandy's analysis would benefit from considering why Protocol Buffers behave the way they do rather than outright attacking the design because it doesn't appear to make sense from a PL-centric perspective. As with all software systems, there are a number of competing constraints that have been weighed that have led to compromises. - D P.S. I also don't beli…

The only thing I was thinking while (half-) reading this article is there's some fundamental misunderstanding about what protobuf is for.

Re: Arguing against using protobuffers

#105
post #93
post #64

Earlier quoted context omitted.

Sure that makes sense if you can write off C++ as a "legacy" language, which is fine if you just can't get your mind wrapped around the scale at which Google operates. Due to the nature of weighted averages, you can't just write off something as being a "Google-only problem". Google and its peers like Amazon and Facebook own a very large fraction of the world's computing resources. I think the overall mistake you and…

C++ is a legacy language when it comes to data representation. Its ideas on what constitutes 'data' are from a different era. That is not a criticism of C++ or its utility. It is just a fact that data representation has changed a lot since the time of C++s development. Products and coproduct types are not academic wankery.

The machine does not think about category theory. The machine thinks about numbers. It can add them together! The way the machine thinks about numbers has not meaningfully changed in 40+ years.

Re: Arguing against using protobuffers

#106
post #63

Earlier quoted context omitted.

This may be easy, but it’s wrong . Endianness issues are just the start. Information leaks due to padding are a big deal. And it straight up doesn’t work if nontrivial data structures are involved.

Endianness issues are something every programmer should be aware of when sending data over the wire. I'm sorry I didn't insert the htons, htonls in my C code. In the Haskell code, endianness is handled by your Storable implementation (you define it after all, and can customize it however you want). I agree my example is somewhat tongue in cheek. The point though is that most languages have standard libraries for deal…

htons, etc are very 1980s, and I’ll make a fairly strong claim: they should never be used in new code, with a single exception. The reason is that an int with network endianness simply should not exist. In other words, when someone sends you a four byte big-endian integer, they sent four bytes, not an int. You can turn it into an int by shifting each byte by the relevant amount and oring them together. And a modern compiler will generate good code.

The sole exception is legacy APIs like inet_aton() that actually require these nonsensical conversions.

Re: Arguing against using protobuffers

#107
post #57
post #38

Earlier quoted context omitted.

My contention with the quoted text is that you probably shouldn't be using elaborate data structures in streams/files. Using DTOs as heap/stack data has bitten me enough times that I'm fairly certain that it's an anti-pattern. It doesn't matter if you're using a quantum binomial tree in a black hole: save it as a 'stupid' map when it hits the network. That way everyone who interacts with your service can decide how t…

> That way everyone who interacts with your service can decide how they want to represent that structure. Protobufs aren't a message format for publicly specced standard wire protocols. They're a serialization layer for polyglot RPC request and response messages. The whole point of them is that you're transporting the same typed data around between different languages, rather than there having to be two conversions (…

In the context of gRPC, it's true that Protobuf is intended to be a wire protocol, but the argument would be more convincing if the code generator toolchain Google fostered created code that integrated better with the languages that people use.

Typically, the data types and interfaces generated by these tools are so poor that you need to build another layer on top that translates between "Protobuf types" and "native types", as you describe in your comment, and shields the application from the nitty-gritty details of gRPC. So investing in gRPC means that when you've generated, say, Go files from your .proto files, you're only halfway done. Protobuf in itself introduces a kind of impedance mismatch, a kind of stupid in-bred cousin whose limited vocabulary has to be translated back and forth into proper language.

So gRPC/Protobuf solves something important at the wire level, but developers really want to productively communicate via APIs, and so what you have is just half the solution.

I wish the Protobuf/gRPC toolchain were organized in such a way that the generated code could actually be used as first-class code. Maybe similar to how parser generators like Lex/YACC or Ragel work, where you provide implementation code that is threaded through the output code.

Re: Arguing against using protobuffers

#108
post #106

Earlier quoted context omitted.

Endianness issues are something every programmer should be aware of when sending data over the wire. I'm sorry I didn't insert the htons, htonls in my C code. In the Haskell code, endianness is handled by your Storable implementation (you define it after all, and can customize it however you want). I agree my example is somewhat tongue in cheek. The point though is that most languages have standard libraries for deal…

htons, etc are very 1980s, and I’ll make a fairly strong claim: they should never be used in new code, with a single exception. The reason is that an int with network endianness simply should not exist. In other words, when someone sends you a four byte big-endian integer, they sent four bytes, not an int. You can turn it into an int by shifting each byte by the relevant amount and oring them together. And a modern c…

You could also use the functions with explicit bit widths, like bswap64 and bswap32.

Re: Arguing against using protobuffers

#109
I think it was Churchill said that "protobufs are the worst form of data serialization, except for all those other forms that have been tried from time to time." I really don't understand why you would ever want to serialize data without a description of what can be in the serialized data blob. And yes, I do care about the difference between a single precision float, a double precision float, or an int64. Thank you, typing system.

Re: Arguing against using protobuffers

#110
post #105
post #93

Earlier quoted context omitted.

C++ is a legacy language when it comes to data representation. Its ideas on what constitutes 'data' are from a different era. That is not a criticism of C++ or its utility. It is just a fact that data representation has changed a lot since the time of C++s development. Products and coproduct types are not academic wankery.

The machine does not think about category theory. The machine thinks about numbers. It can add them together! The way the machine thinks about numbers has not meaningfully changed in 40+ years.

Product and coproduct types are not category theory (what is that?)
Post reply on HN