marionettejs / backbone.marionette

The Backbone Framework
https://marionettejs.com
Other
7.07k stars 1.26k forks source link

Application? #2187

Closed jamesplease closed 9 years ago

jamesplease commented 9 years ago

Does Marionette.Application actually solve any problems? Sure, we love it because of how familiar it is to us, but is it really just code bloat? If these rhetorical questions didn't give me away, I'm unsure of the Application's utility at the moment.

I took some time to make some quick edits to the Application's docs. I tried to make it more clear why someone would choose this thing in the first place. But I want us to ask ourselves what we would lose by removing the Application.

Right now the Application does quite a few things, so it might not seem obvious how one could get by without it. We have a few features that we plan to deprecate, so let me start by defending the removal of those things.

First up: the Channel.

Applications have historically come equipped with an instance of Backbone.Events, Wreqr.RequestResponse, and Wreqr.Commands on them. These three things exist under the keys vent, reqres, and commands.

A little less than a year ago I generalized the idea grouping these three instances together into what's called a channel. A channel is simply a collection of instances of each of these messaging systems. As long as you know the name of a Channel, you can access it anywhere. For Wreqr, the API looks something like this:

Wreqr.radio.channel('myChannel');

I decided that it would make sense to go ahead and make the instances of these systems that exist on the Application come from a Channel. But which one? Well, seeing as how the application is itself a sort of global namespace, I decided that the global channel made the most sense.

This was a good update for a few reasons. You could now access the Global channel from anywhere, without needing to go through the app. But more importantly, you didn't even need the global channel anymore!

By using Channels, you could make as many namespaces as you want, allowing you to logically group, and isolate, related events. Global events, and the global channel, died a symbolic death when Channels were created.

To me, that marked the end of there being an Application channel.

Regions

Regions are the latest feature to be deprecated from the Application, and certainly the most controversial. The tl;dr here is that Regions should be a feature of views. This reduces the number of concepts in Marionette by 1, which is huge. It also makes things easier to explain: in Marionette, your view tree is views all the way down. It doesn't go Application->Region->Views; it's just View->View->View.

In the long-long-term, I can see us moving away from interacting with Regions directly in all situations. In addition to dealing with fewer concepts, this will also make the code within Marionette more compact. We can do all of this while still supporting what we would currently call 'transition regions' and 'multiregions,' so it's a win all around, I think.

@marionettejs/marionette-core are taking my word on this, I think, which I really appreciate. Or maybe they also are convinced that this is a great idea. Either way, we've all decided to deprecate regions on an application.

Initializers

In 97% of situations, I would guess that developers are using these when the Application's start event would suffice. The reason for this, I think, is because historically much emphasis has been placed on Initializers in our documentation, and little emphasis was placed on the start event.

These users should be using the start event because it is more consistent with every other Class within Backbone and Marionette. Initializers, on the other hand, are a very unique thing with a very specific purpose.

In the other 3% of situations, I would guess that some minor code organization could remove the need for Initializers. The thing with Initializers is that they will be fired even after the application has started.

This allows you to do:

app.start();

app.addInitializer(() => {
  console.log('I was already started, but this still fires');
});

Although events don't work retroactively like this, by defining your event handler before starting your application you can achieve the same effect.

Initializers have one more trait which only matters to Modules, but those are a whole 'nother story that I'm equally opposed to right now. You'll have to trust me for now when I say that Modules will do just fine without Initializers (or Finalizers!).

The worst part of Initializers is that they add a whole new concept to Marionette: Callbacks. It's a small set of code, but it adds an unnecessary concept for new developers to figure out (or, more likely, never figure out because of how niche the use-case is).

Anyway, these are old arguments that have led us to deprecate Callbacks and Initializers.

So what's left in the app?

I noted three things in my updates to the documentation:

  1. They provide a namespace; a place to put other Javascript objects
  2. A start method
  3. Automatic integration with the Marionette Inspector

The first of those three things is accomplished by just using var app = {};. The third item on the list cannot be used to justify the existence of a whole class – at least, not to me. The inspector can get by by looking for window.app. It doesn't need for there to be a special Class around; that's simply a convenience.

With 1 and 3 eliminated, that leaves the existence of the Application hinging on the second item. Just how useful is that? I admit that I'm not sure, and I can certainly see it being something that we debate.

A useful thing to do is compare our Application with the analogous feature in Angular and Ember. In Angular, this would be 'bootstrapping' the app. In Ember, it would be the Application's initialize method.

The tl;dr is that each of these libraries do offer an analogous feature, but there's a big difference between Marionette and those other options. In Marionette, nothing happens when you call app.start. In both of those other libraries, many things do happen.

In Angular:

In Ember:

So how much utility is the class offering?

My current thinking is that I would keep the Application around. In Puppets, it looks like this.

I was really just hoping that I would get someone who is adamantly against having such a small thing in the library.

//cc @jridgewell.

samccone commented 9 years ago

The counter to this is that people want the application to do more, and the fact that the application does not do anything (on start) is a problem for people starting off in marionette.

For instance I wrote an entire blog post with the base steps to get an application started http://blog.mojotech.com/kicking-off-a-marionette-js-application/

And here is a gist showing just how much boiler plate is needed to kick off an app http://jsfiddle.net/samccone/F59qp/

This is a real problem, and a hard thing to get over when you are starting.


We have talked in the past about the ideas of subapps, and multiple application architecture as so popularized by @brian-mann (in his case using the marionette modules as life cycle objects). We know that this concept of multiple life cycle application objects is very popular (based on the number of large companies that have embraced the patterns that @brian-mann solidified.

Stripping away the app, into a Marionette.Object, might be the wrong move here, instead I would rather see the Application falling more in line with the rest of the Marionette Classes, and acting as a orchestrator between multiple pieces (RootView*, EventEmitter, Router) that has some very smart and useful defaults.

jasonLaster commented 9 years ago

James nice writeup. I think it's good to question the role app is playing.

Sam I think orchestrator is the perfect word and your list nailed.

Lastly, at etsy we do App.extend and throw lots of shut there like

So ya know, I think it plays a role like other app sub-app objects.

joshbedo commented 9 years ago

100% agree with leaving initializers behind. I feel like only #initialize and #start are needed. So within initialize you could fetch essential data for the app to start and when start is triggered the application glues everything together.

jamesplease commented 9 years ago

Closing. I think Applications should stay...but sub-applications...that's a whole 'nother story! I'll open an issue in a bit.

jonknapp commented 9 years ago

I realize the discussion is closed, but I wanted to add my two cents about gutting the Application object.

When I noticed all of the deprecation warnings for features that used to be available on Application, I got a bit worried. Not that I couldn't pull off what was happening before, as @jmeas points out it's not too difficult to do, but for the fact that I'd now have to add more boilerplate code to every Marionette project. Granted, I can now pick and choose what features my app really utilizes, but it also leaves room for fragmentation in implementation between projects that do make use of the same features.

I really liked having the idea of initializers and a global communication channel that I could count on to be in the same place with the same API in any Marionette application. I'm really glad that there is questioning on whether or not parts of Marionette are available for the chopping block or not, but I am personally hesitant on removing everything that's been deprecated so far unless there are performance or logic issues around keeping them. (aka: I have no problem with removing regions)

jamesplease commented 9 years ago

Hey @jonknapp! Thanks for chiming in.

I am personally hesitant on removing everything that's been deprecated so far unless there are performance or logic issues around keeping them.

Good question. Why should we go about stripping all of this stuff out? The best answer I can give is that Marionette is a 40kb (minified) library that contains no more than 15kb of functionality. We may never get to 15kb, because we don't want everyone to hate us, but the things that are easy to migrate from that give us big size wins are good candidates.

Does that anwer your Q? :open_mouth:

jonknapp commented 9 years ago

Yes it does, and thank you very much for the quick response. I just wanted to speak out as a user who is more interested in consistency between projects rather than library size.

You guys are much more involved with the internals of Marionette than I am, so I trust your judgement.

jamesplease commented 9 years ago

I just wanted to speak out as a user who is more interested in consistency between projects rather than library size.

I get where you're coming from. We're working toward both goals, but maybe from a slightly different perspective. We would like to see Marionette apps across the board be more consistent. I'm sure your Marionette apps look different from mine, and from some other developer.

There are goods and bads to this. The flexibility of Backbone makes it versatile. We never want to lose that, but we do want to offer up some opinions on how you might wish to structure things. And if you disagree, you can always do your own thing with no problems whatsoever. These suggestions will be rolled out in future versions of the docs.

samccone commented 9 years ago

Hey @jonknapp thanks for the concerns!

The motivation for what we are doing is all based around making the API namespace of Marionette smaller while maintaining or increasing functionality.

You are correct we are very much removing several pieces of application, things that have been carried on the code base for a long time but add very little direct value.

By removing certain things, a global channel, a module system (if you want to call it that) ... and several other things we are going to be reducing the variance inbetween marionette apps while at the same time introducing some new powerful concepts that will contain these same features (albeit under a different name). These new concepts will be part of a codified application structure, that will allow you the user to do less work to have a scalable Marionette Application. There is a cry for this as seen in the numerous books, video series and blog posts on how to write applications in Marionette.

Why are we doing this... why are we gutting things. That is a great question. Marionette is in an interesting place, many classes have a mixed set of concerns and some classes simply don't add any value and rather make Marionette harder to learn and harder to use.

We want Marionette to be easy to use, powerful, and a non-magic "framework", hopefully the steps we are taking for v3 will put us in a better spot than we are in now.

Thanks for the feedback and keep it coming, it really helps us!

jonknapp commented 9 years ago

I really appreciate the detailed responses by both of you. It only backs up my confidence that you are taking the project in a better direction.

On Feb 11, 2015, at 8:29 PM, Sam Saccone notifications@github.com wrote:

Hey @jonknapp thanks for the concerns!

The motivation for what we are doing is all based around making the API namespace of Marionette smaller while maintaining or increasing functionality.

You are correct we are very much removing several pieces of application, things that have been carried on the code base for a long time but add very little direct value.

By removing certain things, a global channel, a module system (if you want to call it that) ... and several other things we are going to be reducing the variance inbetween marionette apps while at the same time introducing some new powerful concepts that will contain these same features (albeit under a different name). These new concepts will be part of a codified application structure, that will allow you the user to do less work to have a scalable Marionette Application. There is a cry for this as seen in the numerous books, video series and blog posts on how to write applications in Marionette.

Why are we doing this... why are we gutting things. That is a great question. Marionette is in an interesting place, many classes have a mixed set of concerns and some classes simply don't add any value and rather make Marionette harder to learn and harder to use.

We want Marionette to be easy to use, powerful, and a non-magic "framework", hopefully the steps we are taking for v3 will put us in a better spot than we are in now.

Thanks for the feedback and keep it coming, it really helps us!

— Reply to this email directly or view it on GitHub.

jacobbuck commented 9 years ago

I know this has been closed, but I've just noticed it. In the Application docs under Initializers:

Initializer callbacks are guaranteed to run, no matter when you add them to the app object. If you add them before the app is started, they will run when the start method is called. If you add them after the app is started, they will run immediately.

This is incredibly useful if you're building an async application (like we are here). It might be 3% of use cases, but If you're going to depreciate Initializers and Callbacks, it would be useful if there was something that replaces it so our applications don't break :smile:

jamiebuilds commented 9 years ago

It's really not hard to handle the issue in a cleaner way.

Application.extend({
  initialize() {
    this.doSomethingAsync()
      .then(() => this.doSomethingElse());
  }
});