3 ms·
It's not so much about having another language to learn, it is about sharing code. Suppose you need a meaningful data structure to encode something very domain
by woolion 4y ago
It's not so much about having another language to learn, it is about sharing code.
Suppose you need a meaningful data structure to encode something very domain-specific (for example a dynamic filter you could build with arbitrary boolean operations).
On the front-end side, it will be like a user-facing DSL that you can build with UI elements.
On the backend a recursive algebraic data structure you need to parse and that should go through standard algorithms to obtain (most likely deterministic) results.
So what happens if you have two stacks? You'll need to code a number of these parsing and processing algorithms twice, and each inconsistency will be a bug.
Every time we had the need for something like that in a project, it was done in the backend easily and correctly, but the frontend was lagging and missing features.
So even if the backend language is transpiled to run in a browser, it does change the game to be able to run the correct algorithms directly.
- chatmasta 4y agoMy co-founder and I originally divided our backend/frontend work between each other, and between Python/TypeScript. We found the sweet spot for solving this code sharing problem was GraphQL with code generation on both sides (resolvers from types on the Python side, and types from schema on the TypeScipt side). Most backend services are in Python, using Strawberry for strongly-typed GraphQL resolvers, and client code is in Next.js using TypeScript, Apollo GraphQL, and graphql-codegen for autogenerated types from the GraphQL schema. (We also use Postgraphile as a sort of naive base layer for basic operations, and later added schema stitching to have one GraphQL API in front of multiple services, but that's a longer story [0]). In our company we generally divide the work between frontend and backend developers, and we've found that we usually complete the backend implementation for most features before the frontend anyway, so the GraphQL schema itself becomes part of the spec. But more importantly, GraphQL becomes a unified type layer shared between languages. Backend and frontend developers can each work with the most native features of their respective languages, rather than some subset of each that's supported by the other. The code generation is the secret ingredient, because it lets us draw a straight line of automation from Python types to TypeScript types, with no need to manually write any middleware. Usually frontend and backend is implemented by a different person, but in the cases where one person is doing both, they can literally save a Python file and wait for a chain of hot reloading to have those types available in the Next.js app. It's a pretty awesome setup and I'd recommend looking into building something like it if you're working on a greenfield project and intend to grow your team of developers. But it takes a lot of effort to setup. We arrived here incrementally, originally starting with just Postgraphile and later adding the Strawberry resolvers and schema stitching. If you're an individual, it's almost certainly an unjustifiable amount of work to setup. And it might even be too complicated for an org of multiple people, if they typically have the same full stack developer implement the front and backend of a feature. But for us it's been a great experience overall. And in general, I absolutely love the combination of GraphQL and type-driven code generation, and I would recommend that even individual developers find a way to leverage that aspect of it. [0] https://www.splitgraph.com/blog/graphql-schema-stitching https://www.splitgraph.com/blog/graphql-schema-stitching
- woolion 4y agoIn one case we had a setup close to that. But the case I'm talking about is when you'd have something too complex to encode simply in a standard type system (even with dependent types...). So you have some subtle relationships between different fields, or some specific subtree might be a forbidden substructure, etc. Reimplementing algorithms might be necessary on the frontend for interactive purposes. For most standard use-cases I agree you can have this well-define separations between backend and frontend.
- chatmasta 4y agoYeah, I think I know what you mean, and we have a case of this in our search system where Facets are dynamically defined, and available facets are sent in each response so that subsequent requests can use them in their payload. Ironically enough that is the one backend service that is written in TypeScript, and does share some code with its frontend implementation for just the reason you describe. We also have plugins for importing data where the parameters are dynamically defined with JSONSchema, and we had to do a whole bunch of work to make validation and form rendering work properly on the frontend, while also duplicating it in Python, but luckily we can mostly fall back on standard JSONSchema libraries for that (we also use superstruct on the frontend). We have to rely on getting it "mostly right" in the frontend and then trusting the backend to send back an error message on failed validation (and then we have the problem of rendering that error message in the right place). Generally speaking this still bites us with any GraphQL resolver that has a field returning JSON for whatever reason. The major aspect of code sharing we're still missing is validation of that data between front and back end, and I can see how keeping all your logic in Python would help with this if you're both rendering and validating forms from the same Python code. Of course you can get the same benefit from writing your backend code in JS and sharing that, and I guess that's one of the downsides of our approach, since we lose out on both options.