Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[legacy-framework] [RFC] Blitz App Architecture #73

Merged
merged 3 commits into from
Jun 5, 2020
Merged

Conversation

flybayer
Copy link
Member

@flybayer flybayer commented Apr 2, 2020

The purpose of this RFC (Request For Comments) is to gather as much feedback as possible before building everything outlined in it.

We welcome all feedback, whether good or bad! This is your chance to ensure Blitz meets the needs for your company or project.

👉 View the Rendered RFC 👈

Also, there are now two other open issues for defining more details on Blitz queries and mutations:

  1. Query & Mutation Usage: Define Query & Mutation Usage (useQuery, cache invalidation, etc.) #89
  2. Query & Mutation Error Handling: Query & Mutation Error Handling #87

Please also check those out and provide any feedback!

@fraserdrops
Copy link

Very exciting! I'm interested as to how Blitz compares to RedwoodJS? They seem to have similar approaches: serverless, minimize boiler-plate, monorepo etc.

@flybayer
Copy link
Member Author

flybayer commented Apr 3, 2020

@fmcintosh the single biggest difference is that you develop Blitz apps without building or using an API. Blitz handles all that for you.

Whereas Redwood requires you to use GraphQL and everything that GraphQL requires like manually defining resolver input and output types and defining GQL queries for your client. And if you want to use Typescript you need a whole other layer of complexity and boilerplate to get all the TS types you need.

@flybayer
Copy link
Member Author

flybayer commented Apr 3, 2020

In a direct conversation with me, someone was pushing back on our decision to not use GraphQL, saying we can solve the cold-start/code-size issue by deploying query and resolvers independently.

I decided to answer it here for public benefit:

I definitely considered splitting up graphql queries into multiple functions. This solves the serverless graphql problem, but only for your own app. Third-parties still have to use the monolith endpoint. Unless you add a separate gateway to front everything, but then we are back to a bunch of complexity which we are trying to eliminate in the first place.

All of the boilerplate and ceremony for using Typescript with Graphql is also a huge reason we aren’t using it. Again, probably solvable, but for what end? See following points.

For your own app, you’d primarily use graphql to get a fully typed end-to-end data layer and flexible queries from the frontend. With the Blitz approach, you get both of these without the graphql ceremony.

So the only other thing you need a graphql API for is third-parties. But only if your app actually needs a third-party API — many apps don't. For when you actually do need a third-party API, it’s usually better to design and build a separate graphql API than trying to reuse your own internal graphql api. Especially if you are taking the Blitz & Redwood approach where queries and mutations are created explicitly based on the use cases of your own app. Third-parties are going to have different use cases and they need an API designed for those. Your third-party API will probably also have unique requirements around authentication, authorization, rate limiting, etc.

That said, we might could still be persuaded to switch to GraphQL if we could find a way to use it while still having a similar framework API as described in this RFC and full static Typescript types without a compiler.

@frontsideair
Copy link

I really like the manifesto and the technical direction this project has. I have a suggestion: Currently I'm building a Next.js-based server-rendered webapp and I made a decision early on for it to mostly work without JS enabled.

To achieve this for mutations, every action is wrapped with an HTML form. I built a custom form component to handle submit event without full page refreshes but if JS is disabled it gracefully degrades. The component is not ready for prime time yet and relies heavily on server response conventions (e.g. Location header) but with an integrated solution like Blitz it would work seamlessly.

Another approach could've been to use the API in the current RFC and silently wrap the component with a form to achieve graceful degradation. I don't know if it's possible or feasible but that would be the best of both worlds.

@flybayer
Copy link
Member Author

flybayer commented Apr 3, 2020

@frontsideair that's an interesting approach! I think trying to support disabled JS for Blitz would add too much complexity for too little benefit. I.e. most apps are going to need some fancy JS stuff and there's too few people with a requirement to support JS disabled.

@frontsideair
Copy link

@flybayer I can definitely understand that, I don't imagine anyone actually using the browser with JS disabled. It was a fun experiment to be a good citizen of the web. 😄

@lorenzorapetti
Copy link
Collaborator

I love this! One thing though, in Auto Generated HTTP API you say that every query and mutation gets exposed as an URL but, what if I don't want it exposed?

@flybayer
Copy link
Member Author

flybayer commented Apr 4, 2020

One thing though, in Auto Generated HTTP API you say that every query and mutation gets exposed as an URL but, what if I don't want it exposed?

Well, they have to be exposed at a URL so that you can use them from your React components. How else would you envision it working?

@merelinguist
Copy link

@lorenzorapetti If you didn’t want people to be able to access your api from outside the app, you could probably add some cors middleware. Check out an example of a next app with api middleware here.

@flybayer
Copy link
Member Author

flybayer commented Apr 4, 2020

@merelinguist CORS only affects requests from web browsers. It doesn't affect requests from anywhere outside a browser, such as another server.

@merelinguist
Copy link

Sorry, true!

@lorenzorapetti
Copy link
Collaborator

One thing though, in Auto Generated HTTP API you say that every query and mutation gets exposed as an URL but, what if I don't want it exposed?

Well, they have to be exposed at a URL so that you can use them from your React components. How else would you envision it working?

Yeah that might be a dumb question, I didn't think about RPC. I guess it's fine if we'll provide a way to authorize the requests.

Just to be sure, the generated URLs and RPC will still be used even if i don't want the app to be serverless?

@flybayer
Copy link
Member Author

flybayer commented Apr 5, 2020

@lorenzorapetti yes, we'll for sure have authorization.

And yes, RPC will be used for traditional server deployment too.

@rishabhpoddar
Copy link
Collaborator

For middlewares, we should allow for the following:

  • Apply one middleware to all or a few APIs
  • Allow for arbitrary params (along with req and res to be passed to the middleware). If present, the extra params must be passed on a per API basis.

One use case of this is for authorisation. We can create a middleware that takes req, res and requiredRole as an input. If the user calling that API does not have that requiredRole, then the middleware throws an appropriate error. So here, we want to be able to specify, on a per API basis, what the requiredRole is, and we also want some APIs to not use this middleware.

Now even if the above example is not how authorisation will work in Blitz, there could be other use cases for this kind of behaviour. NodeJS allows for this level of control as well.

@rishabhpoddar
Copy link
Collaborator

We should define what the various default properties are in the Context object vs in the req object. For example, in the Context object, we will have an optional Session object while in the req object, we will have the request URL and headers.

If a user is creating a middleware, how should they decide is something should go in the req object, or Context?

@rishabhpoddar
Copy link
Collaborator

We should also define how the user can do database transactions. This is important since any serious app will require many transactions in them. One more important aspect of transactions is to also be able to specify the isolation level on a per transaction basis (the default one works for most cases).

@cr101
Copy link

cr101 commented Apr 6, 2020

Will new features added to Prisma 2 Client become available to use immediately in the Blitz app or will we need to wait for those new Prisma features to be built into the Blitz core first? In other words, is the Blitz core built on top of Prisma 2? Will we be able to use the Prisma Client directly?

@flybayer
Copy link
Member Author

flybayer commented Apr 6, 2020

We should also define how the user can do database transactions. This is important since any serious app will require many transactions in them. One more important aspect of transactions is to also be able to specify the isolation level on a per transaction basis (the default one works for most cases).

@rishabhpoddar I'm not sure if you are referring to transactions generally like any read/write from the DB, or if you are referring to the explicit transactions that transactional DBs support where all operations within a transaction must succeed or they all fail. I'm assuming you are referring to the later.

Blitz doesn't have any opinions about how you talk to your database. The only opinion Blitz has is that you should use Prisma 2. However, you can freely ignore that and use any other Node.js DB client such as Knex. So you will get whatever db transaction support your DB client has.

Will new features added to Prisma 2 Client become available to use immediately in the Blitz app or will we need to wait for those new Prisma features to be built into the Blitz core first? In other words, is the Blitz core built on top of Prisma 2? Will we be able to use the Prisma Client directly?

@cr101 Prisma 2 will be in the package.json of your Blitz app, so you get everything Prisma 2 provides. Blitz doesn't abstract this in any way.

@flybayer
Copy link
Member Author

flybayer commented Apr 6, 2020

Regarding middleware. @rishabhpoddar

  • Apply one middleware to all or a few APIs

Yes, I think we'll need that. I'd like to wait to define how to do this until we have a better sense of what middleware people will use.

  • Allow for arbitrary params (along with req and res to be passed to the middleware). If present, the extra params must be passed on a per API basis.

One use case of this is for authorisation. We can create a middleware that takes req, res and requiredRole as an input. If the user calling that API does not have that requiredRole, then the middleware throws an appropriate error. So here, we want to be able to specify, on a per API basis, what the requiredRole is, and we also want some APIs to not use this middleware.

My take is that things like authorization should not happen in http middleware. Blitz middleware should exist only for the very few things that need raw access to the http objects. Authentication needs this to access cookies, for example. But then authentication adds the session object to context so it can be used in the query/mutation itself.

Put another way, business logic shouldn't live in http middleware. For example, let's say your authorization is in middleware. Then you need to import a query and use it server side somewhere (not via RPC). You can do that, but your authorization is no longer enforced since it's not going through the http middleware.

Make sense?

We should define what the various default properties are in the Context object vs in the req object. For example, in the Context object, we will have an optional Session object while in the req object, we will have the request URL and headers.

  • The req and res inputs are standard Node req & res objects. Blitz isn't doing anything extra here.
  • The context object is empty by default unless a middleware adds something to it
  • If you use the Blitz authentication system, then session will be added to context (we can implement this via the same middleware system)

If a user is creating a middleware, how should they decide if something should go in the req object, or Context?

Any output from middleware should go in context. Queries and mutations will only have access to context, not req.

@cr101
Copy link

cr101 commented Apr 6, 2020

Regarding authorization, would I be able to create WordPress-style user roles and capabilities as per below?

Screenshot_3

And in the code check if the current logged-in user has permission for a specific task like so:

if current_user_can( 'delete_product' ) {
  db.deleteProduct()
}

@flybayer
Copy link
Member Author

flybayer commented Apr 6, 2020

@cr101, yes something like that! Authn & authz are both TBD. Keep an eye on the #dev-auth channel and Slack. We'll also post RFCs for both of these at some point.

@rishabhpoddar
Copy link
Collaborator

@flybayer

My take is that things like authorization should not happen in http middleware. Blitz middleware should exist only for the very few things that need raw access to the http objects. Authentication needs this to access cookies, for example. But then authentication adds the session object to context so it can be used in the query/mutation itself.

So session management will be a middleware, however, authorisation (seeing if an authenticated user has access to the API) will not be via a middleware. Is my understanding correct?

@flybayer
Copy link
Member Author

flybayer commented Apr 8, 2020

@rishabhpoddar yes, I think that is the best way to do it, but I'm certainly open to hearing arguments for a different approach.

@flybayer
Copy link
Member Author

flybayer commented Apr 8, 2020

Update y'all! I've opened two new issues for defining more details of Blitz queries and mutations:

  1. Query & Mutation Usage: Define Query & Mutation Usage (useQuery, cache invalidation, etc.) #89
  2. Query & Mutation Error Handling: Query & Mutation Error Handling #87

Please check them out and provide any feedback you have!

@IGassmann
Copy link

@flybayer how Blitz would go about serving content to native mobile apps without an API?

@flybayer
Copy link
Member Author

@IGassmann it would do it via an HTTP API, just like how we are doing it with Blitz web apps. There actually is an API, but as a developer you don't have to know or think about it.

@flybayer
Copy link
Member Author

flybayer commented Jun 5, 2020

Now documented at blitzjs.com

@flybayer flybayer merged commit a5a8f42 into canary Jun 5, 2020
@flybayer flybayer deleted the rfc-architecture branch June 5, 2020 15:00
@redbar0n
Copy link

For when you actually do need a third-party API, it’s usually better to design and build a separate graphql API than trying to reuse your own internal graphql api. Especially if you are taking the Blitz & Redwood approach where queries and mutations are created explicitly based on the use cases of your own app. Third-parties are going to have different use cases and they need an API designed for those. Your third-party API will probably also have unique requirements around authentication, authorisation, rate limiting, etc.

Very interesting. Is that based on your own experience? I wonder how common that experience is? Since I have (perhaps naively?) assumed that the big benefit of GraphQL was precisely that third parties might reuse your own GraphQL API (which you in that case would be dogfooding, of course, so it will be naturally kept up-to-date). Do you have some sources that led you to your conclusion? I'd like to read up on it, because if it is a general phenomenon, then it would look like a good argument in favour of BlitzJS over RedwoodJS.

@dillondotzip dillondotzip changed the title [RFC] Blitz App Architecture [legacy-framework] [RFC] Blitz App Architecture Jul 7, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants