-
-
Notifications
You must be signed in to change notification settings - Fork 408
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
Introduce Route Serializers #120
Conversation
All of my feedback was addressed prior to this being posted. 😄 @rwjblue @dgeb @stefanpenner and everybody else following along at home we'd love to hear your thoughts. |
This is pretty similar to a discussion @rwjblue and I had a while back. I think the |
@raytiley thanks for the input. Whether or not to roll query param support into this is definitely an open question. However, it looks like the only relevant QP info for linking to a route are default values which get removed from URLs, but there has been discussion over whether that behavior is even correct/needed. Are you aware of any other QP-related behavior that would be needed before attempting to enter a route? |
Just two cents from one of the "folks at home" 😉 ... As someone who has worked daily on a decently complex production Ember app for 1.5+ years: I'm not entirely sure I understand the need or use case for this. To be clear, I'm sure it exists, but from a pedagogy perspective, the provided examples look identical to me, except now the serialize function is separate. I get that it will improve the handling of asynchrony in routes, but it's not clear why. In my experience training the junior devs on my team, a common struggle learning Ember is the number of concepts to grapple with. Now, there's a decent chance I'm just being dense at the moment and the use case is obvious. But if that's not the case, I'd be concerned about introducing a new "top-level" concept. Especially if I can't grok it quickly, it will be even tougher and seem more arbitrary for the less experienced devs on the team. Like I said above, I'm sure something like this change is necessary. I'd just caution that the reason why it exists should be articulated clearly to even beginners. Certainly the RFC language doesn't need to be written at that level, but just a friendly heads up from the audience. |
@davewasmer imagine you have a BIG app with a user facing section and an admin section. You probably want to link to different admin sections from the main app, but you want to reduce your payload size by not delivering the admin section to the client unless they enter it. This RFC allows you to link to those sections of the app, without them actually being downloaded and evaluated by the client. It's almost entirely for performance, although it can make dealing with larger code bases more maintainable to separate them. |
It's been a long while since I worked on the query params stuff, but basically the Route object as two hooks for serializing / deserializing the query-parsms. I thought they were public, and not sure if they were supposed to be but get turned private in the great private / public change over a few months ago? @machty might know. The idea for overriding them was that you might want to take a a complex object and control the serialization so you could make it prettier than calling |
I would ditto what @davewasmer said. Our app has been around for a while. We almost never have to implement We do have a couple of cases where we have "base route definitions" that similar routes extend, eg: import BaseFileRoute from 'app/routes/base-file';
export default BaseFileRoute.extend({
/* ... */
}); Alternatively, you could create a reusable mixin. Also, you could just create a reusable pure functions for serialization/deserialization based on type. So with all of those existing options in place. I personally think this adds complexity without providing much benefit to most users. And advanced users have some options as mentioned. Edit: I did not fully appreciate the async route loading scenarios when I wrote this. So I acknowledge that aspect of this RFC is new functionality. But I still am skeptical of the number of people who would use this. |
I'm not sure what the solution should be (this may be it), but the problem is as follows:
It's a bit of chicken + egg problem, one approach is to separate out the part of a route responsible for serialization, so that it can be part part of the eagerly loaded bundle. In-practice, although some users rely on this feature (it serves a real purpose), many more users are totally unaware of its existence and get by just fine. I would say adding an extra thing sure is unfortunate (and if it can be avoided it should), but in general this thing isn't on that many critical paths... I did a quick survey of several million lines ember related code on my machine, and this is used 7 times, but those use-cases are legitimate. What does this mean? Adding this concept (if that is the approach taken) has very small downside (as it affects very few users), and an upside of enabling asynchronous loading of engines. |
Then this RFC isn't applicable to you. This RFC exists to explore/solve a real problem blocking async engines, this feature (which you don't use, but is sometimes useful) needs an alternative for us to move forward. |
Could it be solved by tooling? Since we are just extracting a function this is currently on the route to the eagerly loaded bundle, couldn't the ember-engines addon do this at build time? |
This is an option, and was discussed earlier, it wasn't ruled out, but some concerns arose let me share:. The transformation is pretty surprising, as basically this one function can only support some limited subset of the language. Enforcing this by creating a different "thing" at-least reduces that risk, at the cost of another thing (some users may want). |
I think that needs to be mentioned in the RFC. When I seen Ray's response I was going to reply back "ah, engines?" not realizing this is for async engines. I agree with others that it seems a bit overkill but understand if it's needed for async engines, yet another type but what do you do... |
How does putting it in a separate module help? Why not just ship the entire route.js files themselves? I'm not opposed to this RFC but it should be spelled out.
Is it really?
I agree that that in order to talk about a route, you don't need to know how it wants to load its model. But there is a cost to the decoupling. I don't want to have yet another top-level folder in my project directory. Maybe with pods it will be more ergonomic ( |
I guess there are people who heavily put actions in routes who don't like the idea of shipping all the route.js files. I keep the vast majority of my actions on controllers so my routes end up being lean (just a model hook and redirection logic typically). Question: If you had the route.js files in advance, could you preload the models while you were loading the async engine? Are we throwing the baby out with the bath water? |
A separate module which is just a function means that there is no backing object that would need to be instantiated. This is tremendously beneficial especially in apps where all routes extend from some other base class. It also means any miscellaneous dependencies used by the route won't need to be loaded just for this one function.
I agree this is a strong downside, but as others have mentioned, the majority of projects will never even see this.
Potentially, but going back to my first response what happens when your route depends on a parent class and that class imports utilities from other addons (this occurs in an application I work on)? We'll have to draw a line somewhere. Maybe this isn't the right line, but we would have to establish some criteria for it. |
@raytiley if there is any sort of public way to serialize query params then I think we'd definitely want to roll that into this construct.
I imagine so, but aside from @stefanpenner's concern, it would also add more "magic" than I feel should occur. I would definitely think an ember-watson addition should be created to help migration though as I think this is a perfect candidate for it. |
Mine too (using ember-simple-auth). And for the reason I'm ok with this RFC with the current state of things. I do think the next logical thing will be wanting to load the models in parallel with the engine whenever possible. This is similar in spirit to the prefetch RFC. But perhaps that is asking too much. |
We use prefetch currently as well and would definitely like a solution that accommodates it and async engines, but we haven't been able to think of a solution within the current system. |
seems orthogonal |
Requiring my tiny route serialize function to be in its own file seems like pointless overhead for most apps. By not deprecating here, can we keep the common case simpler? It seems to me the existence of the route serializer module could just override a |
This would be fine if the plan was to continue using the entirety of So instead of overriding |
@stefanpenner The spirit of prefetch is to load things faster by doing network requests in parallel. The spirit of loading the engine and its models together is to load things faster by doing network requests in parallel. It's literally the same purpose. If we support the former, we should at least explore whether the latter is possible. Edit: Reworded. |
Instead of yet another file / folder structure, what if the Edit: I'm assuming @raytiley mention of tooling is about somehow magically extracting the existing Route serialize method/function. |
maybe we are talking about the same thing but words are different. I'm going to describe what I believe is motivating the RFC, just to be sure the motivations of the RFC are correctly understood.
Improved concurrency seems like a cool emergent property of async engines, as one can imagine a slim shell that lazy-loads all large slabs of work, but I would say that is an emergent property, rather then a motivating factor. Instead I would say the motivating factors is primarily to reduce up-front load times, by making different aspects of the application pay as you go. So rather then incurrent byte size the penalty of the entire app, one only incurs the penalty of that segment of the application. |
some responses:
|
people about to comment, please be sure to read the first line as this RFC is providing an proposed alternative to an existing feature, a solution is required to decouple an engine's routes from URL generation. Without a solution, one cannot link to engines which have not yet been loaded. Introducing a (rarely used) new entity, although quite unfortunate, is one approach which provides very clear / easily verifiable separation. |
@stefanpenner I agree with everything you wrote. 👍 I was discussing something else entirely (if you bundle all the route.js files that the engine defines you can do much more interesting things at the cost of those bytes, like preloading engine routes' models while loading the engine bundle, redirecting, etc. It seems complicated and I don't feel strongly about it so I'll drop it). I especially agree with the two points #120 (comment). In my experience Route#serialize is very rare. I've seen one among all the apps I've worked on. |
It's still a route related thing... I suppose we should also talk about making Transforms, and possibly an Adapter layer. |
Since the need for custom serializers is rare (as others have said), most developers can ignore the entire concept until an app actually needs it. I'm also not in favor of magic extractions of this method from routes. It's better off as a pure function in a standalone module with clear dependencies. I am 👍 to this RFC. Thanks for writing this up @trentmwillis. It's just what @rwjblue and I had in mind for enabling async engines. |
@trentmwillis although not a firm 👍, treating serialize as a stateless function, used as part of router.map was surprisingly well received at todays meeting. |
This is nice, because you define the dynamic segments in the router, so the serialize makes sense there. If you need a big process, just import a utility.. |
@stefanpenner @dgeb what should be the next step here? I've submitted a POC to get a feel for potential implementation and it seems mostly straightforward. Only unresolved question (that I can think of) would be if there is a use-case for needing state in the serialize function, but I haven't seen any and can't think of any. |
@trentmwillis yes, thanks very much for the POC! I'll review your PR soon and move this along.
Same. I'm pretty sure there's consensus that state should not be needed in the |
Awesome! Thanks @dgeb.
Makes sense. |
@trentmwillis thanks for the POC, looks really great. I've added this to Friday's agenda |
tl;dr; Why don't we just simply use a different helper when we need to use the new serialize function and still have I get the importance of this for async. I had to solve this in a couple of projects where we were lazy loading packages. While having a separate Route#serialize would've helped us, we worked around it just fine in two ways:
This way, everyone can keep using the current Route#serialize in the rare cases when it's needed and only those using engines pay the cost when they have links across packages/engines. The drawback of this is that we're creating some coupling now because the consumer needs to be aware of whether the route is fine with the default implementation for serialize, but most of the time the default implementation works just fine based on Stefan's sample of millions of line of JS code and a quick grep I did on a few large projects, that's rarely even used. An additional workaround, that I never used, but thought of it for cases where we wanted to override serialize was to rely on a loose function that we can pass to the
Then routeNameSerializer is simply a prop in your component to a pure function that you can import.
The problem with that is that now your component needs to know about everything that is being linked by its template and we still need to know of cases where we actually override the serializer, which ideally the component should be unaware of. This can be solved by simply relying on the serialze function proposed on this RFC defined as part of
|
} | ||
}); | ||
}); | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The core team discussed the pedagogy and strongly favors separating the definition of the serialize
function from the main route map. This is analogous to how we separate the declaration of initialize
functions in initializers.
@trentmwillis could you please change the example above to something like:
// app/router.js
function serializePostRoute(model) {
// this will make the URL `/posts/12`
return { post_id: model.id };
}
export default Router.map(function() {
this.route('post', { path: '/post/:id', serialize: serializePostRoute });
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. Will update this and also update the proof-of-concept PR to reflect this pattern as well.
@MiguelMadero if |
589a0ff
to
bc18da3
Compare
Agreed! On Wed, Mar 16, 2016 at 1:10 PM Dan Gebhardt [email protected]
|
Updated the examples given to reflect separating the serialize function declaration and passing it into the router map. |
@trentmwillis thanks for updating. I think that was the only blocker to merging this RFC as well as your POC. I'll try to keep this moving this along ... |
I agree RE: landing the RFC, but I think the POC needs a bit of work (some compat tests, deprecations, etc), though it is possible that has been added since I last reviewed it... |
@rwjblue I've added a deprecation and tests for the cases I could think of. Just let me know of anything else that is needed. |
Hey folks, @trentmwillis. At the last meeting I brought this up, but I didn't follow through here. Sorry :-/ I'll publish the relevant notes and link. Updating the examples to move the serialization declaration out of that map was indeed the last blocker on merging this RFC. It would have been nice if "Pedagogy" was also updated referencing that change, as that style is related to how we present this API to end-users. I'll make a followup commit to address that. Mergin'! Thanks for your thoughtfulness and effort as we work toward consensus all 🎉 |
Sorry to be late to the party.. as this is already merged i just wondered if anyone would know if this use case was considered or not?
In this case, it can easily be extrapolated by looking just at the route file, why some extra serialization is needed to perform. When the serialization is moved over to the router.js, it will not be clear why a serialized id must exist at So, since this is already merged and seems to be the agreed path forward, I will ask.
|
I think it's a non-issue because how you get the id is really the same. How many levels you have to go down will just depend on the route. This refactor in general is a loss in clarity, since the serializer is related to the model and the router has no visual context to that, but it was either this, or have it in it's own file, which is even less clear, because you don't get the |
Refresh and restructure the Ember CLI Guides
Refresh and restructure the Ember CLI Guides
Rendered.