Skip to content
New issue

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

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

Already on GitHub? # to your account

Removing Modules in favor of subapps #2026

Closed
cmaher opened this issue Oct 26, 2014 · 44 comments
Closed

Removing Modules in favor of subapps #2026

cmaher opened this issue Oct 26, 2014 · 44 comments

Comments

@cmaher
Copy link
Member

cmaher commented Oct 26, 2014

As of v2.2.2, Marionette has two separate classes for managing the lifecycle of the application and it's components: Marionette.Application and Marionette.Module. This proposal aims to offer a path for replacing Modules with the Application and subapps.

Relevant issues

Current State of Application and Module

Marionette.Application is used to start the app and its submodules. It additionally provides a global hook to access modules and the global event channel.

Marionette.Module provides a means of starting a portion of the application, including submodules. Modules have the option of immediately starting with their parent, and they can also be stopped (something that can't be done to the application). Modules can be created by extending the Module class, or by using the module method on the application, which provides access to the App, for global module access, and the rest of Marionette's dependencies.

Much of the existing capabilities of Marionette.Application and Marionette.Module are of dubious value in the days of AMD/CommonJS/ES6. Providing global access to the world is very hard to maintain and scale, often requiring manual management of file load order and causing problems when trying to find all the places a particular component is used. Attaching everything to a namespace under a single variable is no longer considered good practice, and it should not be a focus of the framework. Additionally, the fact that there are so many ways of creating modules is confusing.

Using the Application to manage the full app lifecycle

Historically, it was only possible to have one Application due to the fact that all instances shared the global channel. Now that the channel is configurable on a per-application basis (#1528), it is trivial to support multiple Applications on the same page. This invites scrutiny of the divide between the Application and Module classes in the area of app lifecycle management.

There is currently discussion to remove RegionManager capabilities and event channel properties from the Application in v3. That would leave the Application with only three behaviors:

  1. Starting the application (and any attached submodules)
  2. Creating modules with Application#module
  3. Providing a global hook to for attaching things to

I would argue that only the first behavior is useful, and that the third one is downright harmful.

Meanwhile, module behavior will remain largely unmodified and support the following:

  1. Starting themselves and any submodules
  2. Starting with the parent module or application
  3. Stopping themselves and their submodules
  4. Attach themselves as a property of the parent module

All of which but the last are useful and good practice. Interestingly enough Modules will do everything useful that Applications do, but in a more straight-forward manner (the code for making Modules start with the Application lives in the Module).

At this point, it no longer makes sense to have two classes managing application lifecycle. I am proposing that we only have one class to manage application lifecycle, the Application, and that Modules should be replaced with subapps attached to an instance of Application. All subapps would also be instances of Application.

Subapp API

The usage of the Application and subapps would look as follows

var JellyApp = Marionette.Application.extend({});
var PeanutApp = Marionette.Application.extend({});
var ShellApp = Marionette.Application.extend({});

var MyApp = Marionette.Application.extend({
    // can also be a function
    subapps: {
      jelly: JellyApp,
      peanut: {
        applicationClass: PeanutApp,
        butter: true,
        startWithParent: false,
        subapps: {
          shell: new ShellApp()
        }
      }
    },

    // can also be a function
    subappOptions: {
      edible: true
    }
});

var app = new MyApp();
var jellyApp = app.getSubapp('jelly');
var peanutApp = app.getSubapp('peanut');
var shellApp = peanutApp.getSubapp('shell');

jellyApp.edible; // true
peanutApp.edible; //true
peanutApp.butter; // true

app.start();
jellyApp.isRunning(); // true
peanutApp.isRunning(); // false
legumeApp.isRunning(); // false

peanutApp.start();
legumeApp.isRunning() // true

app.stop();
jellyApp.isRunning(); // false
peanutApp.isRunning(); // false
legumeApp.isRunning(); // false

Version Plans

  • v2.3
    • Deprecate the behavior whereby an Application extends itself with all of it's options at initialize (separate issue coming).
    • Deprecate initializers on Application and Module, assumed from deprecation of Marionette.Callbacks in Adds deprecation notice regarding Callbacks. #1965 (separate issue coming).
  • v3.0.0
  • v3.x
    • Add support for subapps in Application
    • Add stop to Application
    • Module will extend application, and its only additional behavior will include whatever is necessary to support Application#module
    • Deprecate Module and all supporting methods, e.g.Application#module
  • v4
    • Remove Module and all supporting methods
    • At this point, I certainly hope no one will care enough about the discussed Marionette.Namespaces behavior to actually consider supporting it (but it is a possibility).

Not removing modules immediately in v3 might place a bit of a burden on the code, but I think it is very important from the standpoint of not isolating/splitting the Marionette community. Many Marionette tutorials emphasize modules, and large swaths of production code almost certainly depend on them.

@paulfalgout
Copy link
Member

I'm not quite sure if this is included in the description of Modules or namespaces, but one of the features of a module I've found useful is easily breaking up things for the same module without having to pass around dependencies. Perhaps this is a bad practice, but thus-far it's been useful.

I will mention our current stack doesn't have commonjs or requirejs, but we're moving in that direction soon, and I've been concerned that this pattern just won't work in the future.

However we currently will have one module in a file like something.js

App.module('Something', function(Something, App, Backbone, Marionette, $, _) {
    Something.Object = ......
});

And in a something_views.js

App.module('Something', function(Something, App, Backbone, Marionette, $, _) {
    Something.ChildView = ......
    Something.CollectionView = ......
});

And in our old school asset merger we can list the object or the view file first.. doesn't matter.. and in a couple of instances we have a different build for mobile than for desktop where the something.js is the same, but the something_views.js is different.

@cmaher
Copy link
Member Author

cmaher commented Oct 28, 2014

@paulfalgout That was the idea behind Marionette.Namespaces. I, for one, don't think it's a good idea for Marionette core to provide a module system when there are so many better alternatives. Marionette aims to make it easier to scale Backbone Apps, and App.module can't scale. I would hope that no one would want this behavior when v4 rolls around, but there's always the possibility of still supporting App.module as Marionette.Namespaces in a separate repo.

It might even make sense to split this out in v3, but I think it's wiser to provide as easy of a transition to a new major version as possible.

Thoughts @marionettejs/marionette-core ?

@paulfalgout
Copy link
Member

It's possible this issue for me is solved with documentation and examples.

With modules it was relatively easy to read the marionette documentation alone and understand seemingly decent patterns for setting up a large, scaleable app.

Without prior experience with some other modular system, it makes starting a marionette app daunting as there's not a clear path to building modularly without first learning another fairly unrelated system. Plus there are choices in various module systems and without knowing the caveats as they apply to Marionette could further increase the barrier to entry.

I for one have used both requirejs and commonjs sparingly and at first glance all of the changes being made to marionette's apps and modules sound good, but I can't see yet a clear path between my code and fully utilizing another modular system that does not include complicated dependency configuration that I don't currently deal with using the built in modules.

So I suppose I have two questions:

  1. Will there be any reasonable patterns for building marionette apps moving forward that does not rely on prior knowledge of requirejs / commonjs?
  2. If requirejs / commonjs use with marionette is considered best practice, can we get sufficient documentation show clear patterns for large, scaleable apps, prior to or along with the release?

My biggest concern is not having a very clear understanding for the effort involved in moving from v2 to v3. While I don't want to be dependent on some inferior modular system, I can't very well start from scratch either.

@cmaher
Copy link
Member Author

cmaher commented Oct 28, 2014

It's far from complete, but we do have this repository showing samples for requirejs and browserify.

And your concern about having to convert your application is totally legitimate. At my job, we decided to gradually transition to requirejs until we made it to a critical point where we could write new features entirely using AMD.

But the core team knows that Marionette.Module is a commonly and heavily used feature, which is why I think we should continue to support it, at least until v4 lands in the distantish future. Around that time, it might be appropriate to spin off Marionette.Namespaces, depending on the what the future holds.

I think that kind of covers your second question. As for your first question, I'm of the opinion that there should not be a module system baked-in to Marionette. There are currently three very robust, very usable module systems, and at least one of them represents the future of JavaScript. Marionette should rely on people managing their own modules instead of including a core feature for something that quickly-becoming (or already is) an anti-pattern.

@stephanebachelier
Copy link
Contributor

@cmaher I really enjoy a plan to remove the Module for Application. But I don't really like the concept of subapps: it reminds me of module with a different name but a better API.

I would prefer the usage of multiple decoupled applications. So I don't think we need to have the subapp property.

It's already something I'm working on for big projects I'm working on and I hope to be able to communicate on this soon.

@cmaher
Copy link
Member Author

cmaher commented Oct 29, 2014

@stephanebachelier I would like to see what you've been doing with multiple apps. This issue (and the gitter) would be a great place to discuss them.

The purpose of subapps is to tie the lifecycle of an app to it's parent, while still allowing for independent apps.

For example, if I have a Calendar app that I use on it's own, but I also have a Reservation Scheduling app that needs a calendar, I can just plug a Calendar subapp into the Reservation app, along with a little configuration (selector for the main calendar region to attach to). With that, starting/stopping the Reservation app would also start/stop the Calendar app embedded in it (but it would not have an affect on any other Calendar apps that might be on the page).

@jamesplease
Copy link
Member

it reminds me of module with a different name but a better API.

This is what I'm thinking after having read through this; same idea, new name.

I don't think that we need to provide a declarative solution to tying together multiple Marionette.Applications. Even further, I'm not a fan of using these 'container' objects to group sections of the app. I'd rather see us move toward Router-organized apps, which I think @thejameskyle also agrees with (correct me if I'm wrong there, James).

@stephanebachelier
Copy link
Contributor

@jmeas Also agree on the Router organized apps. It's already something I'm currently using in two large applications. But it's not sufficient. The approach I've use in these applications is :

  • router based applications, which are started when a route is triggered
  • long running application, which are application that are not dependent on routes. They can be started at any time and can be used for lots of things from being responsible for some region like updating a feeds, managing sockets, ...

My two large applications are closed source but I'm working on releasing part as open source. They share the same abstract base code. Hope to release something soon. It could be a base to this discussion.

@stephanebachelier
Copy link
Contributor

To complete my post, these large applications are using Marionette.

@cmaher
Copy link
Member Author

cmaher commented Nov 1, 2014

@jmeas I think router-organized apps could be too limiting. The main things I want out of this are (1) a consolidated API and (2) the ability to manage the lifecycle of applications together, while providing the option for them to function the exact same separately.

Picture a site consisting of a bunch of different components on a single page that all interact with each other or respond to the same set of controls on a page. Now imagine that you can take any of these components and add them as a widget to a dashboard of all other kinds of widgets that can come from any page across your site (so that you have widget 1 from page A and widget 2 from page B on the same dashboard).

I want each of these widgets to function as independent applications that receive different inputs depending on where they're loaded (i.e. take page-level controls on their page of origin, take dashboard-level controls on a dashboard), but from the concept of a page (something that can be routed to), I want the original page to be an application, and I want the dashboard page to be an application. Stopping an application (e.g. by invoking a route to a different part of my site) should stop all of the other widget applications on the page.

Not allowing for subapps would require that I manually stop every single application on a page, even though I know that the lifecycle of this app is tied to the page-level application.

@jamiebuilds
Copy link
Member

@jmeas I still think there is a place for life-cycle objects other than routers. For example, in the wires repo I have a modal module which can be started and stopped, nothing is tied to routes, but it's still useful to be able to kick that off when I need it and shut it down when I don't.

@jasonLaster
Copy link
Member

Yep. I see the router as Url based entry-points for life-cycle objects. We could/should think about starting other objects that are not tied to Urls, like popups.

@jamiebuilds
Copy link
Member

I wonder if we could/should introduce a more generic Service class which could be used for all sorts of stuff (UI or business logic).

var PopupService = Service.extend({
  channelName: 'popup',
  onStart: function() {
    this.channel.comply({
      'open' : 'open',
      'shut' : 'shut'
    }, this);
  },
  onStop: function() {
    this.channel.reset();
  },
  // ...
});
var UserService = Service.extend({
  channelName: 'user',
  onStart: function() {
    this.channel.comply({
      'login' : 'login',
      'logout' : 'logout'
    }, this);
  },
  onStop: function() {
    this.channel.reset();
  },
  // ...
});

This is a pattern I see use adopting at CloudFlare in the next few months.

@cmaher
Copy link
Member Author

cmaher commented Nov 2, 2014

@thejameskyle I see Services as being a perfect use case for multiple Apps (+subapps). With talk of consolidating our View behavior, it seems appropriate to consolidate our lifecycle components.

@jamiebuilds
Copy link
Member

@cmaher I think I'd rather see Services and Router implemented as the lifecycle components of Marionette next.

I've begun thinking that "Application" and "Module" are overloaded terms that are hard to define constraints for.

Service Oriented Architecture is a well-defined pattern which is easy to explain and that many have adopted. I would like for this part of Marionette to get the Radio treatment and be semantic implementations of what you're really trying to do.

@cmaher
Copy link
Member Author

cmaher commented Nov 2, 2014

When I think of 'Services', I think of singleton stateless constructs that can either answer questions (how many cats do I have?) or do something (feed this cat). I'm coming at this from the server side (Spring MVC in particular), but it seems that Angular services are pretty much the same. I think Services could definitely have a place in Marionette (provided they have/warrant a different API than what the Application has), but I don't see them as a replacement for Applications/multiple applications/subapps.

@jamiebuilds
Copy link
Member

If we were to strictly define Services as singletons (aka not leave that up to the user), why would we not want the same things for "Applications"? When would those not exist as singletons that you can start and stop?

@cmaher
Copy link
Member Author

cmaher commented Nov 2, 2014

In my widget case: there can be many widgets of the same type but with different configurations in a single dashboard. In my booking example, there could be multiple fancy Calendar apps loaded at any one time (think of booking a round-trip ticket: you could have two side-by-side applications, one for your journey there, one for your journey back).

@jamiebuilds
Copy link
Member

I'm not sure if that's a really good use case to build an entire API around. Users could already do that today with Marionette.Object in place of an Application. I'm doubtful widgets like that would have routers anyways.

@cmaher
Copy link
Member Author

cmaher commented Nov 3, 2014

Widgets wouldn't have routers. Right now, they're essentially modules. Marionette.Object doesn't (by default) fit into any sort of automated lifecycle-- it must be explicitly started or destroyed. What I'm trying to get at is that it's seems odd to support an almost useless Application, in addition to a Module that (1) does everything Application does (+ more good stuff), and (2) actively supports some truly bad practice code. MyApp.Entities.Events.Bookings is the exact opposite of what a modern JavaScript framework should be providing.

@bgaillard
Copy link

Hi, we've developped several Marionette applications and we've never used the Module class.

At the time we begun to use Marionette I think their was a confusion between the Marionette module class and AMD / UMD / CommonJS modules. For example MyApp.module("MyModule", function(MyModule, MyApp, Backbone, Marionette, $, _){ ... } is very similar to define(['app', 'backbone', 'marionette', 'jquery', 'underscore'], function(app, Backbone, Marionette, $, _) { ... }). I know... Marionette modules are not the same but when you're beginning to code with JS this can be confusing.

So because Marionette is a JS Framework and because in JS a "module" has a specific meaning AMD / UMD / CommonJS, Harmony, etc... I think the term Module is confusing. In my opinion the term Sub-App is not good too because a Marionette Module could be "anything", not only an "app". A Marionette Module (correct me if I'm wrong) is a general peace of software which is plugged / unplugged to / from an existing application. It could be a low level component used to communicate with a server, a graphical Widget, a specialized view, a sub-app, etc...

Finally I also think the term service is too close to Spring, Java EE, etc... services (see Service Layer).

For all those reasons my personnal preference :-) would be to rename the term Module to Component.

Marionette V4 / V5 😄, "Bundles" ?
To accelerate our work what we would like to have with Marionette is a sort of "component repository" with ready to use components (Widgets, Sub-Apps, etc...). A "component" would be a package with the current Marionette Module "entry point" class (similar to the Zend Module.php class, the Symfony Bundle class, the Java OSGI Bundle class, etc...).

I think a Marionette Module is "only" an "entry point" / "a loader" for something bigger : a Marionette "Bundle". Bundles should encourage developpers to develop Marionette components and share them with the community.

In our projects each time we create a Marionette application with Twitter Bootstrap we develop the same peace of Marionette+Twitter Bootstrap code to manage modal dialogs again and again and again... It would be great to have an "official" and reusable Marionette+Bootstrap OK/Cancel dialog (or Bundle). This bundle would be very easy to pull from GIT (using bower for example) and then very easy to integrate and use inside an existing Marionette app.

In Marionette their is a documentation to explain how to develop the "entry point" / "loader" of a "Bundle" (the Module class).

In the long term I think we (as developers) need additional instructions to develop the code attached to this "entry point" (i.e the whole Bundle). This Marionette Bundle development guide / tool would be a project separated from the core Marionette Framework (because it will inevitably be opinionated) with : a Yeoman generator to start the dev of a new Marionette bundle quickly, a grunt/gulp task to package it, instructions to organize files and directories, instructions to manage events (with a Backbone.Radio channel for example), instructions to test it, how to specify inputs, output, other good practices, etc...

@jamiebuilds
Copy link
Member

@bgaillard Can you open up a separate issue for the distributed components and bundles issue. We need to keep this conversation focused.

@jamiebuilds
Copy link
Member

@cmaher My problem is just that "Application" is a hard thing to define scope for, as it stands now it is simply a container for modules and regions, which makes very little sense. "Module" is an overloaded term which doesn't do what most people initially thinks it does. "SubApps" is still just as arbitrary as "Application" and isn't implementing any particular pattern.

I think we can both communicate the purpose of these objects better and define their scope in a much simpler way by sticking to existing patterns.

MyApp.Entities.Events.Bookings is the exact opposite of what a modern JavaScript framework should be providing.

I don't see how app.getSubapp('peanut').getSubapp('shell') is any different from this other than hiding the internal objects away.

Marionette.Object doesn't (by default) fit into any sort of automated lifecycle-- it must be explicitly started or destroyed.

I don't want everything to switch to this start/stop pattern. For most things, I believe it's much better to simply instantiate an object once you need it, and toss the whole object away when you don't. It seems bizarre to new an object and then call start immediately afterwards.

Lifecycle to me means that I have an object which must always exist, but only must be actively doing something for a chunk of the time.

  • Routers must always exist so that their routes exist. Eventually the browser will navigate to one of their routes which will then enter the router and enter the route. Then eventually the browser will navigate to another router and it will then exit the route and exit the router.
  • Services must always exist so that another piece of the application can communicate with them. Eventually something will send the service a message, and it will start up, do it's thing and probably shut down unless it needs to save state for later (ie. A modal will stay open until it has been shut).

For child views (widgets), those should simply be created and destroyed how they always have been, if you want to move some logic out of the view then you should get a Marionette.Object to do that.

I believe services will encourage people to move business logic into containers and embrace messaging patterns.

@paulfalgout
Copy link
Member

tl;dr: Most "apps" I use don't have a lifetime outside of a "running" state, and "apps" I have that have listeners active when not "running" are global services (ie: modals) or the Router/Controller

Though I've been second guessing much of my organization, our app uses the following structure. I don't really think I'm saying much that @thejameskyle didn't just say.

We've got the main marionette app. It mainly just bootstraps data and starts history because it's first.

We have what we then internally call subapps that represent the major sections of the app and each of these sections are divided into what we call our parent and child apps. Each of these "apps" is a router, a set of events, and a controller that's shared between the parent and the child. The parent is typically a list and child is a chosen item's detailed view, and in larger views they can be viewed side-by-side.

A parent app will always clear the child app, but loading a child app will throw an event which will load the applicable parent app via an event that does not change the URL.
Based on a route or event we'll destroy the current "app" and load an "app" (just an Object which loads a view) into the parent and maybe child app regions. For each parent or child apps they'll have various objects that we create/start if we need them, but we've extended Marionette.Object such that you can register an object after creation with it's parent, so when the parent closes it also closes. It was too easy to forget to put an obj.destroy in an onClose and have orphaned listeners. Some objects are very app specific and will never be reused, others are more global, or are more component like (ie: datepicker)

And then we also have services like modal and alerts that are always globally available and listening for commands.. those listeners currently get setup on their module start.

For the most part I guess I have a hard time distinguishing between what I'd need from whatever an app or subapp will be verses a simple extend of a Marionette.Object... and if the app solution is simple enough.. I'm not sure when I'd use Object.
I do have a need for services and my router/controllers to handle events even when that app is not "started" but for the most part I explicitly "start" an Object with "new" and setup listeners with initialize that are killed when the object destroys itself or the Object that instantiated and registered it to itself gets destroyed.

One feature of current modules we use that I can't quite figure out in browserify is that we swap out our view modules for our mobile build. So with our current antiquated asset merger we have two json asset files where we maintain what files and the order for our two js builds. All of our Objects are shared and they instantiate views associated to their module by name, and depending on the build will either load the desktop or mobile view. So we really only use the module namespacing such that view modules can say they're apart of a particular module object. While we do current use nested namespacing, it has no actual utility other than allowing us to conditionally load different views for one object without some sort of conditional require()

@bgaillard
Copy link

@thejameskyle Can you open up a separate issue for the distributed components and bundles issue.

Ok thanks, here it is : #2054

it is simply a container for modules and regions, which makes very little sense
move business logic into containers and embrace messaging patterns.
It seems bizarre to new an object and then call start immediately afterwards.

Perhaps the Application is not really an "application" but simply a Container ?

For example in Java the goal of the Spring Container is simply to manage objects and the life-cycle of those "objects".

Making a new and then calling a start takes part of the object life-cycle and would be the role of a container.

Services must always exist

Services would be an other kind of Marionette Pattern / Specialized object. We could enforce the fact that every object registered in the "Container" must extend the Marionette Object class.

I don't know wire.js a lot but I think their approach can inpire us (even if DI is out of scope here).

@cmaher
Copy link
Member Author

cmaher commented Nov 4, 2014

I don't see how app.getSubapp('peanut').getSubapp('shell') is any different from this other than hiding the internal objects away.

The difference in MyApp.Entities.Events.Bookings is in the creation. Building JS with Marionette modules (and not using amd/cjs/es6) will result in the need to manually order your files.

For child views (widgets), those should simply be created and destroyed how they always have been, if you want to move some logic out of the view then you should get a Marionette.Object to do that.

So I show a view, that starts up an object that determines what it does? Yes, that's doable, but it's completely backwards. A view shouldn't determine what sort of logic it gets, logic should be determine how a view is displayed. Having to remember to teardown that Object in onDestroy every time isn't going to be fun either. The alternative seems to be to force all of your logic into a view, which is messy and annoying to test.

Services must always exist

This is pretty much guaranteed to not scale. If we make them always available (i.e. accessible, but lazily instantiated), then that should be good.

I don't want everything to switch to this start/stop pattern. For most things, I believe it's much better to simply instantiate an object once you need it, and toss the whole object away when you don't.

I agree with not wanting a start, but something will still need to be around to make sure it tears down (so destroy?)

I really think we need more of a working lifecycle that doesn't fit into the view lifecycle. I wanted to make Application this object, since it's something that people already use, and I didn't want the community to think that Marionette v3 is pulling an Angular v2.

Based on a route or event we'll destroy the current "app" and load an "app" (just an Object which loads a view) into the parent and maybe child app regions. For each parent or child apps they'll have various objects that we create/start if we need them, but we've extended Marionette.Object such that you can register an object after creation with it's parent, so when the parent closes it also closes

This is pretty much what I was getting at with the subapps, but using automatically managed Marionette.Objects instead. I think it goes along well with what @thejameskyle was saying about not having a start method, which I also like.

My purpose for subapps was for symmetry with the Application object. I would like to be able to pull out any subapp and make that a stand-alone application that just runs by itself (maybe needing a few more manually-configured options). And when it comes to the Application, I don't think we should hastily remove start (hence, start and stop/destroy stayed around). But if we do get rid of start, it could be Marionette.Objects all the way down, and, as long as Objects can automatically be destroyed when a parent Object is destroyed, I think that's a good thing.

@paulfalgout
Copy link
Member

it could be Marionette.Objects all the way down, and, as long as Objects can automatically be destroyed when a parent Object is destroyed, I think that's a good thing.

I think this is the primary concern.

I also like the idea of being able to define an optional channelName which will attach a radio channel to the object without having to manually set it up in an initialize. But that might not be a pattern for the library to dictate.

@cmaher for marionette modules, I'm pretty sure as long as MyApp is created first, the order in which you load the modules is entirely inconsequential. If you load MyApp.Entities.Events.Bookings first it will create modules for MyApp.Entities.Events and MyApp.Entities and then you can create as many modules of the same names later and they'll essentially combine to a single scoped module prior to running.

Personally on their own I don't see using marionette modules to be that bad apart from that they aren't a standard. The namespacing equates to a virtual directory structure with commonjs. You shouldn't access a module via crawling up and down a namespace anymore than you should be require(../../../some/other/module)

That said I don't think there's a good argument for keeping them, except that there is some functionality the namespacing provides that seems difficult to replicate in browserify.

@cmaher
Copy link
Member Author

cmaher commented Nov 4, 2014

@paulfalgout

The load order problem I'm talking about is about module 1 using module 2 at definition time, but module 2 is loaded second. This can be avoided with very careful coding, but module loaders solve dependency management for you.

@jamiebuilds
Copy link
Member

The difference in MyApp.Entities.Events.Bookings is in the creation. Building JS with Marionette modules (and not using amd/cjs/es6) will result in the need to manually order your files.

Are you saying you want to instantiate these object when you getSubapp() them?

So I show a view, that starts up an object that determines what it does? Yes, that's doable, but it's completely backwards.

I agree that's backwards, but I meant to create an object that can create one or more views and manage them (kinda like a contr***er but I'll avoid that name like a 4 letter word).

This is pretty much guaranteed to not scale. If we make them always available (i.e. accessible, but lazily instantiated), then that should be good.

It scales quite nicely actually if you build them to be essentially empty objects most of the time (only doing something when you need them to). You generally would limit the number of services you create anyways. But I can definitely get on board with lazily instantiating them.

I agree with not wanting a start, but something will still need to be around to make sure it tears down (so destroy?)

I actually would love if every object in Marionette had a destroy method.

I really think we need more of a working lifecycle that doesn't fit into the view lifecycle. I wanted to make Application this object, since it's something that people already use, and I didn't want the community to think that Marionette v3 is pulling an Angular v2.

Heh, I don't think we have the time or resources to fuck up that bad. However, you're suggesting we change the primary purpose of Application anyways. So I'd rather deprecate it and give something new for people to transition to over time rather than release a major version and break an existing object. That falls much more in line with what we've been doing, and is a much cleaner migration path.

@cmaher
Copy link
Member Author

cmaher commented Nov 4, 2014

Are you saying you want to instantiate these object when you getSubapp() them?

I'm just talking about load order being important, like how we have these in an important order.

// file1
app.Module("EntityUser", function (module, App) {
  this.entity = App.Entities.Entity
});

// file2
app.Module("Entities", function (module, App) {
  this.Entity = Backbone.Model;
});

EntityUser is being defined before Entities and will therefore break. Were these loaded via amd/cjs/es6, dependencies could be explicitly stated and they would be loaded in the correct order

Heh, I don't think we have the time or resources to fuck up that bad.

Here's hoping 😄

So I'd rather deprecate it and give something new for people to transition to over time rather than release a major version and break an existing object.

So what do you think of Marionette.Objects all the way down? Or at least something that can tear down its children that's not a view (like how modules currently work)?

Really, all I want is (1) don't support that app.Module syntax and (2) still provide a means of tearing down nested non-view objects (like a LayoutView for logic).

@jamiebuilds
Copy link
Member

I'm just talking about load order being important

😄 Yeah I think the closer we can get everything to just using cjs/amd/es6 modules the better. No more building APIs like it's 2005.

So what do you think of Marionette.Objects all the way down?

I actually don't mind this at all (I think we should rename it to Class but that's a separate issue). I still think there's a place for something a bit more. An object which encourages use of the messaging patterns in Radio, and can be treated as a higher-level entity.

Really, all I want is (1) don't support that app.Module syntax

I'm in full support of that.

still provide a means of tearing down nested non-view objects (like a LayoutView for logic).

Unless we start enforcing a particular structure in a similar way that ember does, I don't think there's a perfect solution for this. Routes will help as it's an opportunity to drop a whole section of the app off at once.

The other important thing to note here is that in Backbone apps, most people treat view's like controllers and put all sorts of business logic in them. I've very rarely see people move that kind of logic into their own class objects (although sometimes you'll see mixins).

@cmaher
Copy link
Member Author

cmaher commented Nov 5, 2014

So I started thinking about an addition to Marionette.Object (Class) that will tie another object's destruction to the given objects destroy:

var obj = new Marionette.Object();
var subobj = new Marionette.Object()
obj.addDestroyable(subobj);  // could probably use a better name
obj.destroy(); // first destroys subobj, then obj

This echoes the pattern where a RegionManager will empty it's regions, and a region will destroy it's view.

Also

An object which encourages use of the messaging patterns in Radio, and can be treated as a higher-level entity.

👍

@jamiebuilds
Copy link
Member

Hmm, I'm not sure about marking an object for destruction ahead of time like that is all that great either. I'd rather people just know when to destroy their objects.

var FirstObject = Mn.Object.extend({
  initialize() {
    this.secondObject = new SecondObject();
  },
  onBeforeDestroy() {
    this.secondObject.destroy();
  }
});

// vs.

var FirstObject = Mn.Object.extend({
  initialize() {
    this.secondObject = new SecondObject();
    this.addDestroyable(this.secondObject);
  }
});

I think the former is a bit clearer about what is going to happen. If your code gets to a higher level of complexity, you're probably going to want to add to onBeforeDestroy and onDestroy anyways.

@rhubarbselleven
Copy link
Contributor

My problem is just that "Application" is a hard thing to define scope for, as it stands now it is simply a container for modules and regions, which makes very little sense. "Module" is an overloaded term which doesn't do what most people initially thinks it does. "SubApps" is still just as arbitrary as "Application" and isn't implementing any particular pattern.

👍

Why not just let Applications start and stop other Applications?

@jamiebuilds
Copy link
Member

Why not just let Applications start and stop other Applications?

@rhubarbselleven Do we need a specialized object for that? I don't think so.

The complexity is in the details. Application currently has a RegionManager... so is Application a view type? That seems weird to me, but then again "Application" is a fairly arbitrary term which means a lot of different things to different people, which is why I said it's hard to define scope for.

@glenpike
Copy link

We've been working with an Application, which currently only contains the main router, but uses a lot of Modules. The main pain point for us was trying to use Modules with RequireJS and not having a dependency on the main Application object on which to call "module()" - unit testing Modules in isolation was important to us.
With hindsight, there are possibly other ways around this - having read Derick's Browserify Article I am partially inclined to agree with him, but we also want lazy-loading and instanciation of Modules in our application.
My main request / requirement for Module or SubApplication definition would be enabling a separation of the dependencies and allowing things to be joined at runtime.
For what it's worth, I just wrote a standalone ModuleStore which duplicates Application.module functionality, enabling Modules to declare themselves and attach to this namespace, then the App can set itself as the base for the namespace at runtime: https://github.com/glenpike/marionette-module-example

@jamiebuilds
Copy link
Member

You can use a real module loader to do that, that should not be the responsibility of Marionette.

@glenpike
Copy link

We are using an AMD loader - requirejs - to do the loading of our files with Modules defined. The problem is the fact we have to call Application.module to create our modules.
We have been using the Callback function definition which would normally mean a dependency on Application in our AMD modules (hence the workaround)
We are now refactoring to use Module Classes, which means the Application.module could be called with the loaded AMD module, but I'm not sure if this split seems to have a bit of code smell about it - hence the request to separate the dependency on Module/SubApp "creation" away from the application into a utility function / object.

@samccone
Copy link
Member

@glenpike
Copy link

Cool, thanks.

@jamiebuilds
Copy link
Member

You should be refactoring your app to remove that though.

@jasonLaster
Copy link
Member

hey @glenpike - I think the module class Module.extend approach is a nice one. I've found that it'll set you up well going forward as the Router, App, and Module are better coordinated

@trusktr
Copy link

trusktr commented Feb 24, 2015

In #2228 I suggested something similar to the declarative subapp structure at the top of this issue, but for LayoutViews. I like the pattern because it could make it really easy to learn an app's structure without having to dig through a ton of files first.

@cmaher But what happens when a subapp defines it's own subapp structure? Would the subapp's subapps object get mixed with the subapps defined by it's parent? Or would the parent's subapps structure get entirely overriden at that point?

@rafde
Copy link
Member

rafde commented Sep 7, 2016

🏠 keeping. Closing since Mn3 removed modules.

# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

No branches or pull requests