Readit News logoReadit News
eat_veggies · 6 years ago
> That means that when one side of the API changes, the other side won't even compile until it's updated to match.

Coupling your front end and back end in this way can give you false confidence in making API changes. If you control both sides of the API on a web app that doesn't have too many users yet, then this can be very productive, but consider this situation:

You've made a breaking API change on the back end, and thanks to your type system, you make the corresponding front end changes as well, which is great. You deploy your code, and the back end changes take effect immediately. Visitors who were in the middle of a session are still running the old front end code, and their requests start failing. Cached copies of the front end start failing too.

You can engineer around this but it's better to have a system that doesn't make introducing breaking API changes too easy. It should be painful.

joshAg · 6 years ago
When you control both front-end and back-end, you don't have to make a breaking change that will affect anyone at all, if you're willing to do a multiple deploys and maybe wait a little bit.

If your want to change from A to B, your first deploy is deploying B without stopping A from working. It could be a totally new endpoint, or you could just throw a version field into the request body or querystring. Once everyone has switched off of A, you can remove it from your codebase so only B remains without actually affecting anyone's session.

If you don't want to pollute all your querystrings or request bodies with version fields, you could have an optional newVersion boolean field that only needs to appear during a switchover or make a temporary endpoint for updated clients to hit instead of the old endpoint:

  # state at start  
  /foo <- uses API A

  # deploy new endpoint
  /foo <- uses API A
  /foo/new <- uses API B

  # wait for everyone to swtich to /foo/new, then deploy API to /foo, removing support for API A (this could be split into two deploys if you wanted)
  /foo <- uses API B
  /foo/new <- uses API B

  # wait for everyone to switch over to /foo, then remove /foo/new
  /foo <- uses API B

flohofwoe · 6 years ago
MMO games like Guild Wars handle this problem by deploying a new server version in parallel to the running old servers and keep sessions in progress connected to the old servers. New sessions will connect to the newly deployed servers, while old sessions eventually "dry out". Old server processes with no active connections shut themselves down.

IMHO that's an easier solution than having to implement a backward compatible API or other API versioning solutions.

jkaptur · 6 years ago
And if you do a gradual rollout, newer clients can connect to older backends! And if clients can talk to each other, there can be THREE versions involved. Also: rollbacks.

I’m curious if anyone has a system for managing this that they love. I’ve pretty much only seen painful ways.

hamandcheese · 6 years ago
At <dayjob> we use flow types and graphql, so like OP our frontend will fail to compile if we make a breaking change. To assist with the backward-compatible-during-deploy issue, we additionally have a teensy bit of tooling that comments on our PRs to indicate dangerous API changes.

It's not perfect (I've ignored the comments before, thinking I knew better... I didn't), but it seems to help.

It wouldn't be difficult to make it more sophisticated, and then completely block PRs it knew weren't backward compatible, but we haven't seen a strong need to do that yet.

n0w · 6 years ago
I think in an ideal world you would do API changes to a live system in much the same way as you do with a live Database Schema: Carefully, and in a backward compatible manner.
BiteCode_dev · 6 years ago
If you don't have typing, you'll have this problem too.
MaxBarraclough · 6 years ago
> You can engineer around this

Right, just version the API, or have the app refresh itself when appropriate. Isn't this a fairly straightforward issue to address?

Also, as BiteCode_dev points out, this issue has nothing to do with whether you use a static type system.

ttty · 6 years ago
In https://game.bitplanets.com I have `gameVersion` that will change when I make breaking changes. Then the client kind of understands that the client version doesn't match the server version and it will refresh.
dguo · 6 years ago
Note that Stripe is working on a type checker for Ruby: https://sorbet.org/

I second the sentiment though. I wouldn't choose to use plain JavaScript over TypeScript for any significant project.

You might ask why I wouldn't just use a more fully typed language like Java. My main response is that I love having the flexibility to choose how strict the types should be. Sometimes, typing something out fully is not worth the trouble. Having escape hatches lets me make that choice. While I enjoy using types to turn runtime bugs into compile time errors as much as possible, it's not the right thing to do 100% of the time.

tluyben2 · 6 years ago
> it's not the right thing to do 100% of the time.

What is an example of this in your experience? I am trying to think of one, but after 25 years of programming, I cannot say I have ever encountered a case where it was worth using ‘an escape hatch’ that did not bite us later on. Maybe it is the type of software/clients I work with, but ‘the trouble’ is not really something I have seem before I think.

JMTQp8lwXL · 6 years ago
Foregoing types isn't like other escape hatches. Sometimes you'll get odd, hard-to-understand errors from the typescript compiler. On my team, we just use Babel, so we can write TypeScript (and get the benefits of types) without have 100% statically-correct code. Getting from 99% to 100% statically correct is time spent detracting from other more valuable contributions to the business, that don't have a meaningful impact on code quality. Diminishing returns.
edem · 6 years ago
The case is when you are lazy and choose to introduce technical debt into your codebase by using the escape hatch.
yyyk · 6 years ago
Both C# and Kotlin have a 'Dynamic' type for just that escape hatch. IMHO, That's a better way over TS - since types might not be the right thing 100% of the time, but they are 95% of the time.
Analemma_ · 6 years ago
I programmed professionally in C# for about eight years and in that time I can count on one hand the number of times I saw the ‘dynamic’ keyword. Nobody seems to think it’s worth the effort or confusion.
bravura · 6 years ago
Kotlin is a backend language tho?

I saw other commenter say that the benefit of using TS in the frontend and backend, then DB updates in the backend also update the frontend code: "The beauty of Gary’s setup is changes to his database update the types for the server side Code AND client side code. The value of that comes from using the same language in both platforms"

Can you get this sort of benefit with Kotlin? How?

flyinglizard · 6 years ago
Not a pro C# dev but I use it quite a bit - IIRC the correct keyword for that situation would be 'var' which is similar to 'auto' in C++. It just statically infers the type from the assignment, while 'dynamic' actually does runtime casting. I use 'var' all the time, I use 'dynamic' only when working with JSON objects and such.
uyuioi · 6 years ago
Or you could just use Crystal Lang and get types and 20x performance instead.
bananabreakfast · 6 years ago
Does Crystal run rails?
ncphillips · 6 years ago
That is a cool project but you’d still have to keep it in sync with your frontend code. The beauty of Gary’s setup is changes to his database update the types for the server side Code AND client side code. The value of that comes from using the same language in both platforms
rblatz · 6 years ago
You aren't bound to use the same language on both platforms to get this functionality. I had this running 3-4 years ago using C#, EntityFramework, and a d.ts generator that I forgot the name of.

A change to the DB schema just required me to refresh our edmx from the DB schema and then build.

gregkerzhner · 6 years ago
Having partial type safety is like having a no peeing section in the pool.
jjeaff · 6 years ago
That's not true. There are plenty of cases where certain pieces of software is cordoned off and/or non-critical that would not affect the rest of your stack if they fail.
dguo · 6 years ago
To your point, I do think it depends on the team and their ability to make good judgment calls.

But type safety is a spectrum anyway. One could say that not having dependent types is like having a swimming pool without a lifeguard.

Should every swimming pool in the world have a lifeguard? Probably not.

xsmasher · 6 years ago
It makes it really easy to add type safety to parts of a legacy project as you go along; for new projects you can enforce type safety with a linter or stricter compiler settings.
cdata · 6 years ago
A more accurate remix of this analogy (for the TypeScript case) is that it is like building a wall around the part of the pool that you actually swim in, and allowing that folks can pee all they want as long as it is outside of the boundary you built.
simplify · 6 years ago
Would you say the same about incomplete testing?
lmm · 6 years ago
If you control both ends of an API, there's no reason not to use Thrift or equivalent (gRPC) rather than untyped JS. Even if you don't believe in typing in general, an API boundary is exactly where it's most important to make sure both sides agree on what the interface is.
ledauphin · 6 years ago
GraphQL works well for this also if that's an option in your stack.

It advertises lots of benefits but the enforced schema types are worth the price of admission even if you're doing simple CRUD with no graphs.

jpdb · 6 years ago
Calling gRPC endpoints from the browser isn't something that works very well today.
lmm · 6 years ago
Calling thrift endpoints from the browser works great. I had heard that gRPC was a thrift equivalent but haven't used it myself.
nice__two · 6 years ago
Absolutely! If you don't do this, you're just building the next unmaintainable distributed monolith.
leipert · 6 years ago
Do I read the chart correctly and their whole Codebase (minus dependencies) is less the 15k lines of code? Porting 6.5k likes of ruby in 2 weeks sounds reasonable, but how doing migrations 10x or 100x that size is far more challenging.
myth_drannon · 6 years ago
I think Gary is the only guy working on the project and it is a new project no wonder he migrated it super fast.
orange8 · 6 years ago
TypeScript has been on HN for different reasons over the last few days. The main thing that has stood out, from the flame wars erupting over it is that it is a truly divisive idea. To some, it gives the same securities and checks provided when working with a statically typed language. To others, it curtails the power and expressiveness inherent in JS, the dynamic, functional language it compiles down to.

TypeScript does provide a lot of benefits, but for it to truly succeed and take over in the JS world would mean it has to truly also reflect, and enable JS's functional and dynamic roots. JS got where it is by being JS.. an extremely flexible and accommodating language. HTML got this far today by doing the same, just google XHTML if you doubt that. So for TypeScript to succeed where CoffeeScript failed, this may be the direction it needs to lean more towards: being less divisive, and inviting all kinds of programming paradigms to the party.

That, after-all is how JS succeeded.

scarface74 · 6 years ago
JS got where it is by being JS..

JS got where it is solely by being the language built into browsers.

orange8 · 6 years ago
Is that really all it is though? Cases in point: Java Applets, VB, Flash, Dart ...

All of these were at one point or another "built into the browser", but where are they now? Give credit where it is due, the success of the web as a platform lies not in its technical superiority over alternatives, but in its inclusiveness and flexibility. Any tech that is trying to replace HTML, CSS and JS in this regards will seriously have to consider and accommodate this, or suffer the same fate of hundreds of other pretenders to the throne. Long live the king! Long live open, approachable and flexible tech!

Exhibit A: List of very different and diverse languages that compile to JS ( https://github.com/jashkenas/coffeescript/wiki/List-of-langu... ). If this does not demonstrate the flexibility and malleability of the language, then I do not know what does. Take web assembly for example, which has been around for over five years now, and was specifically designed as a "compile to" language. How many languages compile to web assembly in comparison?

Any tech that is as divisive as TS is simply not going to get far. Flash ActionScript was massive 10 years ago compared to any alternative to JS tech today, and where is it now? The creator of TS even quotes ActionScript as one of the main inspirations for TS. ActionScript even had a more powerful version of Reacts JSX (ES4), where is it today? CoffeeScript was all the rage 5 years ago, JS simply absorbed all its good ideas, where is it today? The things that last, that stand the test of time are the things that are flexible and accommodate different ways of doing things. For your beloved TS to stand the test of time, it has got to accommodate the whole JS eco-system, not just those who favor the static object oriented way of doing things.

myth_drannon · 6 years ago
How times are changing. The person who is famous for his amazing Ruby/Rails screencasts rewrote his Ruby backend to TS...
xupybd · 6 years ago
Wow porting the entire backend in two weeks is impressive. I guess he knew the domain well and both languages well but I'm impressed.
cameronfraser · 6 years ago
How does one deal with remote data in typescript? I really like typescript, but not having any guarantees on the returned data is kind of frustrating.
a_humean · 6 years ago
It is actually quite frustrating compared to some other languages, and more often than not you see that its just `JSON.parse` and hope for the best. I wish we had something like Rust's Serde or Haskell's aeson.

If you want to validate data coming over IO then your options are data validation libraries such joi, io-ts, and yup. You have to write seperate data validators on top of your types. io-ts has a way of deriving types from validators, but io-ts is often seen as quite intimidating being built on top of fp-ts.

No matter how carefully you maintain strict typing within your typscript project the moment you hit IO everything is basically `any`.

Some projects like openapi-generator might generate some validators for server respones, but I've not seen any good generators that do actual validation.

I'm not sure if apollo-graphql does responses validation? Does anyone know?

willberman · 6 years ago
I second the use of iots. It has a decent learning curve but is very worth it in the end.
eropple · 6 years ago
Plenty of web frameworks can validate via JSON Schema, using ajv under the hood. (I use Fastify.)

io-ts is pretty scary, though. I like the `runtypes` library but it doesn't help me express my types in a way clients can consume, which, enh.

seer · 6 years ago
Apollo does indeed validate responses as well as requests. If your server tries to send an object that doesn’t match the schema, you’re going to return just a 500 error, which means your clients can trust the graphql types implicitly.

Since in my current job I can’t use graphql but have to type everything in openapi, Had to resort to writing my own lib to get to the same functionality for a REST backend in TS - https://github.com/ovotech/laminar

Ezku · 6 years ago
My personal take is that io-ts _is_ typescript’s aeson, but if you’re scared of the baggage it comes with then how about giving zod a try? https://github.com/vriad/zod
andy_ppp · 6 years ago
There are things like io-ts that do exactly this: http://github.com/gcanti/io-ts
moltar · 6 years ago
Yup plus see other projects in the same vein:

https://github.com/moltar/typescript-runtime-type-benchmarks

latchkey · 6 years ago
For my current project, it is handled by GraphQL. Using a combination of type-graphql and code generation, I have fully round trip static types on both client and server, by only defining my model once. Even my graphql queries are checked.

It is a bit of a pain to setup, but once it is done, I've found that it works pretty well.

[1] https://github.com/MichalLytek/type-graphql/

[2] https://graphql-code-generator.com/

0XAFFE · 6 years ago
I use a combination of class-transformer[1] and then class-validator[2].

First transform the JSON into proper class-objects and then validate the object for correctnes.

IMHO this has nothing to do with TypeScript because in your runtime all the type information is gone and you need to validate the remote data never the less. TypeScript can help you structure your validations but you need to write them.

[1] https://github.com/typestack/class-transformer

[2] https://github.com/typestack/class-validator

Geeflow · 6 years ago
I use tcomb-validation[1] to validate incoming data. The downside is that it cannot use TypeScript types so you have to define the schema twice.

For me, the duplicated types turned out to be less of a hassle than initially anticipated. In many cases, I ended up with different data structures anyway because the data is transformed in the client. I think of them as transport types and client types.

[1]: https://github.com/gcanti/tcomb-validation

postalrat · 6 years ago
Let it fail and make sure you are getting notified.
JoshuaScript · 6 years ago
https://quicktype.io/ is a nice solution
k__ · 6 years ago
AWS Amplify has a code generator that uses GraphQL to generate client side TypeScript and server side APIs
k__ · 6 years ago
Just found out they're using this:

https://graphql-code-generator.com/