Hacker News new | past | comments | ask | show | jobs | submit | benfle's comments login

Is it a good idea to base your GraphQL schema on your database schema?

Isn't the point of GraphQL to decouple your interface (who should not break) from your backend implementation details?


> Is it a good idea to base your GraphQL schema on your database schema?

You don't. Hasura (and Postgraphile and PostgREST) employ in-database modeling tools called views. Views provide the same modeling abstractions that ORMs provide.

https://www.postgresql.org/docs/12/sql-createview.html

A view is a canned query with a name, and when you get down to it, all an ORM is doing is producing, usually poorly, a query from a named abstraction. Same thing. So why have two pieces in two languages when you can have one?

> Isn't the point of GraphQL to decouple your interface (who should not break) from your backend implementation details?

Views provide the same or better decoupling. They database is aware of them, they are type checked, you can't even make a view that references an unknown column, for example.


You would never expose your database directly to the whole world.

Either it is an internal service, only consumed by your own products, and then it's fine. Or you have an API gateway to control who and how your services are queried.

The API Gateway is your interface who should not break. Your services communicating with your database (through Prisma, Hasura, any ORM, ...) can and will break.

When you base your GraphQL schema on your database schema for your internal services, you get rid of one long and annoying step of putting glue everywhere. This is especially true in a microservice architecture, and even more in the corporate world where you are expected to produce code quickly.


I don't see those quotes as contradicting Kay's point about data.

My interpretation of what Torvalds and Pike are saying is that we should spend time designing our programs to find the right way to think about our problems. And this is done by finding the right data structures and their relationships (architecture) so that the code (algorithms) is minimal.

This is very close to what Kay was advocating with object-oriented programming (OOP). The data that Alan Kay wanted to get rid of is what Clojure people calls place-oriented programming (PLOP). Which is funny because they often use PLOP to mean OOP. What Kay meant by "data" is setting and getting values in memory slots (assignment). He realized that moving data around like this did not scale because most of your code will be busy doing those mundane things. This is the "code" and "algorithm" part in Pike's and Torvalds's quotes. Your software is cluttered by code moving things from one place to another because you don't have the right "data structure" or "architecture" or "object design".

Unfortunately, a lot of modern OOP languages are PLOP and make it even worse by encouraging developers to create a class for every single piece of data they want to compute with. Calling `setName` on an instance of a Person class is not OOP but the "data" that Kay wanted to get rid off in the first place.

So I think they're all talking about very similar things but with slightly different terms. The goal of programming should be to find the point of view that will give us the most leverage to solve our problem. We should spend more time designing our programs to find the right concepts as opposed to start coding as soon as possible with whatever abstractions we're familiar with or that our programming environment makes available.

My personal favorite about how to get conceptual efficiency of our software is from David P. Reed's "Get the verbs right". See https://users.cs.duke.edu/~rodger/articles/AlanKay70thpoints...


Alan Kay and Rich Hickey (creator of Clojure) had a discussion about the idea of "data" here: https://news.ycombinator.com/item?id=11945722


Allen Wirfs-Brock summarizes it perfectly: https://twitter.com/awbjs/status/1107678004757430272


Aren't we lucky to have stumbled upon the "correct tool for the job" at our first attempt.


I mean, you could argue that just as with writing, code is emergent from our biology. Brains are naturally wired for language and symbolic thought. Once computation was defined and made technically possible, perhaps the current form of programming was simply inevitable. Not that other things aren't worth trying, of course.


I don't see the difference between sending a Person instance and sending a map of keywords about a person. The coupling is the same.


If my function only needs to know the "age", then why am I having to fill out my Person class with all the other stuff? Why, if I have facts about a Cat in hand, must I coerce it to a Person? These are hoops you're typically jumping through when you're dealing in ADTs.


If "age" is an important property in your system shared among different kinds of entities then you need to have an Interface or a Protocol to retrieve the age of an entity.

The same way you would create a keyword in Clojure to represent the age of an entity (e.g. ':entity/age') that can be put in a map describing a person or a cat.

In both cases you minimized the interface between your modules and you have less coupling.


Not in OCaml. You can have a function like that:

let printAge object = print object.age

And it'll just check if anything passed to printAge has a field "age".


Well, in Haskell, this seems like a case where you'd want a typeclass for getting the age out of your type.

More generally, though, it seems like row-types might be a form of static typing that would fit Rich's preferred style of programming.


That doesn't seem to describe any hoops I've ever had to jump through when using Haskell. Can you give concrete examples?


I just did. Having a Person vs. Cat taxonomy. The claim is about ADTs, not Haskell. When a "name" property will do, why do we need to introduce an ADT? Why do we need to taxonomize?


Then you can use a "HasName" typeclass. Admittedly that adds a bit of boilerplate (in one single place).


I think the constant replies of "Oh there's a way to deal with that." Miss the point. You should keep asking yourself, "Am I fixing a problem that didn't need to be there?" Sometimes, the answer is: No, I do want this structure, and it's worth it overall to write interfaces, etc. to add some polymorphism or dynamism to it where needed. In lots of cases, though, you're just writing stuff to accommodate the language. In lots of languages I feel like I'm fighting an internal battle between static-ness and dynamism. Start with static types or classes, then add interfaces or typeclasses, oh and overload these functions. Now make sure these other things things implement this new interface so they can participate, etc.

Sometimes it feels like a real burden for not much gain over just passing around the basic data (a name, an age) I wanted to deal with to start with. Clojure's proposition is that in many many cases, not getting fancy with the data or over-engineering your problem representation will lead to simpler programs that are easier to maintain, giving you an alternative route to safety and maintenance instead of type-checking.


> If my function only needs to know the "age", then why am I having to fill out my Person class with all the other stuff? Why, if I have facts about a Cat in hand, must I coerce it to a Person?

If your function only needs to know the age, then why would it take a Person or a Cat at all, instead of just accepting an age parameter? But assuming you have a reason, who says you do need to coerce anything or add any dummy data? You don't even have to go very niche to get that functionality, eg in Typescript:

    class Person {
      age: number
      constructor (age: number) { this.age = age }
    }

    class Cat {
      age: number
      constructor (age: number) { this.age = age }
    }

    const printNextAge = (thing: { age: number }) => {
      console.log(thing.age + 1)
    }

    // These all work
    printNextAge(new Person(12))
    printNextAge(new Cat(23))
    const someRandomObject = { age: 10, colour: 'green', weight: 'heavy' }
    printNextAge(someRandomObject)

    // These don't:

    const lady = { name: 'carol' }
    printNextAge(lady)
    // error TS2345: Argument of type '{ name: string; }' is not assignable to parameter of type '{ age: number; }'.
    //  Property 'age' is missing in type '{ name: string; }'.

    const caveman = { age: 'stone' }
    printNextAge(caveman)
    // error TS2345: Argument of type '{ age: string; }' is not assignable to parameter of type '{ age: number; }'.
    //  Types of property 'age' are incompatible.
    //    Type 'string' is not assignable to type 'number'.
Now, if the function takes a Person, then the reason you need to fill out the rest of the stuff is because it probably wants an entire Person, not just their age. The fact that the function can tell the compiler it needs an entire Person (and not a Cat) and have it ensure that it only gets valid Persons doesn't stop you from doing anything a non-buggy program should do, it just makes the language more expressive. Even in a wordier language with a less powerful type system like Java, which obviously isn't the gold standard for static typing (and where for some reason your function was still taking an object instead of just an age int and leaving it up to the caller to extract it), it's as simple as saying:

    interface Aged {
        int getAge();
    }
and adding 'implements Aged' to your Person and Cat classes.


I think Rich is talking more about data structures than data abstractions here. In particular, he's saying that he favors sound data models (RDF, datalog, relational) to manipulate information in our programs over specific data structures.

To me it is related to Alan Perlis's saying on the power you get from uniformity:

"It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures."

I totally agree with him on this point and I also agree that for some of us a lot of our programs are about manipulating static information structures. But let's not reduce programming to that.


Aren't statically typed languages "sound models"? Aren't ADTs a form of data structure?

I think his point is much more in the realm of abstraction. RH always seems to be trying to minimize the delta between thinking and coding/problem-solving. ADTs are in most cases cumbersome. Static languages introduce more impedance and coupling than benefit to the industry business problems we're solving via code. These are arguments against certain attempts at abstraction, it seems to me. But I may be missing what you're saying.


Other things that occur both naturally and in simulations: revenge, jealousy, violence, territoriality, oligarchy, thumb sucking...


The author is obviously more interested in telling entertaining stories (in the boardroom) than telling the truth.

Tufte is not only about data viz. and powerpoint bashing. Integrity is central to his work.

I would not trust someone who present me a graph like in the second example (whether as a pie chart or as a bar chart). There is no way to know if the classes have the same attendance. Hence his conclusions could be completely wrong.


I had the exact same reaction. It is unfortunate Chris didn't take time to respond to your comment...

It is always interesting to see different object systems being used. I just hope we will get a honest review of it in a few months after it has been used to develop a large application like Light Table.


It's nothing wrong with using something like that, but it's hardly novel in any way. I have no doubt that one can develop a larger application with it...


That's effectively unfortunate to compare your design to OOP because we tend to have different opinions of what OOP is. If you see OOP simply as message passing and polymorphism then your "all-e" and "renderer" functions just reimplement these ideas (as opposed to have your function simply assign different values to your data). Why isn't your "render-player" not a method?

It would have been interesting to go into more details about the CES design. For example, how do the components interact with each other? How is "position" updated when interacting with "walker" and "jumper"?


What would be very interesting to talk about (instead of entering into the details of an innocent analogy) is a toolkit that would make very easy to create new higher level languages on top of JavaScript.

Unfortunately, JavaScript and its VMs have limitations that would not allow us to create very performant languages for some domains but I think we could have a lot of other useful higher level language to program (the web).


Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: