Skip to content

Instantly share code, notes, and snippets.

@mbriggs
Created September 27, 2012 18:17
Show Gist options
  • Select an option

  • Save mbriggs/3795517 to your computer and use it in GitHub Desktop.

Select an option

Save mbriggs/3795517 to your computer and use it in GitHub Desktop.
/*
* Currently (as I understand it), BBCloneMail is organized as a hierarchy of modules,
* where all modules communicate via req/resp, or commands, and all internals are implementation
* details.
*
* for the purposes of conversation, I am calling modules that are conceptually bodies of code as
* "sub applications", and modules that interact in those bodies of code as "modules"
*/
// psudo-code
module('contacts', function(){
app.respondTo("contact:find-all", controller.findAll, controller)
})
/*
* This is not a problem for smaller things. But the level of business logic for the thing I am
* about to build is non trivial, and I don't think it would scale.
*
* Lets say you are in a "family" of modules. If you have one module playing the role of "repository",
* requests like "contacts:find-all" are fine. But lets say you have 5 repositories, and each
* one is implementing 10 methods, and you also have more use-case style controllers for the same
*/
module('contacts-repository', function(){
app.respondTo("contact:find-all", controller.findAll, controller)
})
// controllers are fine for simple-ish things, but when you have more complex coordinations and
// business logic, it can be nice to break out module services that model a use case
module('contacts-deleting', function(){
controller = {
delete: function(contact){
var condition = app.request("some-module:condition-met?")
$.when(condition).then(function(){
if(contact.foo && condition){
app.execute('contact:destroy', contact)
} else {
app.execute('notify:error', "Some message")
}
})
}
}
app.respondTo("contact:delete", controller.delete, controller)
})
/*
* obviously, the above example isn't even close to as complex as these thing can easily get. Also,
* maybe you would want to say that each request should have the actual module name that receives it
* as prefix (although this breaks the abstraction a bit). But by flattening all module "methods" into
* a single namespace, I think you are losing a lot of clarity into what is actually happening, and
* trading it for a high level of decoupling.
*
* In simple cases this is probably preferrable, but let's say you have 30 or 40 different interactions
* that are all passing through the sub app as an event bus. I can see that tradeoff no longer being
* worth it.
*
* I think a more traditional service locator approach would solve this problem. So would DI, but
* I think service location fits better with marionette modules
*/
// quick and dirty service locator implementation
;(function(Marionette){
var services = {
store: {},
get: function(name){ return this.store[name] },
set: function(name, value){ this.store[name] = value }
}
_.bindAll(services)
Marionette.Application.prototype.service = services.set
Marionette.Application.prototype.service = services.get
}(Backbone.Marionette));
// in each of the different module files
app.service("contact-repository", ContactRepository)
app.service("notifier", Notifier)
app.service("some-module", Something)
// use case
module('contacts-deleting', function(){
// ask the sub app to find the services this current service needs to do its job
var repository = app.locate('contact-repository'),
notifier = app.locate('notifier'),
something = app.locate('something')
controller = {
delete: function(contact){
var condition = something.isConditionMet(contact)
$.when(condition).then(function(){
if(contact.foo && condition){
repository.destroy(contact)
} else {
notifier.error('Some Message')
}
})
}
}
app.service('DeletingContacts', controller)
})
/*
* I think the benefits are
*
* 1 - we are back more into a "interactions with other objects" world, rather then a "everything is like a private method world". This can make it easier to understand what role a given thing plays, just by looking at what its dependancies are.
* 2 - it can make it easier to find out who is using a given thing -- you only have to grep for who is asking for it, not every message it responds to
* 3 - it exposes a bit more of the structure of the dependancies, which can give you hints on where to go next when tracing the code in complex scenarios
*
* downsides
*
* 1 - it is a bit less decoupled (although still pretty damn decoupled), and you stop thinking about interactions within a sub app as a flat thing.
*
* 2- tests aren't quite as nice, you need to now pass in stubs rather then just test messaging. i think this could be a good thing too due to clarity, but it is definately more typing
*/
/*
* Currently (as I understand it), BBCloneMail is organized as a hierarchy of modules,
* where all modules communicate via req/resp, or commands, and all internals are implementation
* details.
*
* for the purposes of conversation, I am calling modules that are conceptually bodies of code as
* "sub applications", and modules that interact in those bodies of code as "modules"
*/
// psudo-code
module('contacts', function(){
app.respondTo("contact:find-all", controller.findAll, controller)
})
/*
* This is not a problem for smaller things. But the level of business logic for the thing I am
* about to build is non trivial, and I don't think it would scale.
*
* Lets say you are in a "family" of modules. If you have one module playing the role of "repository",
* requests like "contacts:find-all" are fine. But lets say you have 5 repositories, and each
* one is implementing 10 methods, and you also have more use-case style controllers for the same
*/
module('contacts-repository', function(){
app.respondTo("contact:find-all", controller.findAll, controller)
})
// controllers are fine for simple-ish things, but when you have more complex coordinations and
// business logic, it can be nice to break out module services that model a use case
module('contacts-deleting', function(){
controller = {
delete: function(contact){
var condition = app.request("some-module:condition-met?")
$.when(condition).then(function(){
if(contact.foo && condition){
app.execute('contact:destroy', contact)
} else {
app.execute('notify:error', "Some message")
}
})
}
}
app.respondTo("contact:delete", controller.delete, controller)
})
/*
* obviously, the above example isn't even close to as complex as these thing can easily get. Also,
* maybe you would want to say that each request should have the actual module name that receives it
* as prefix (although this breaks the abstraction a bit). But by flattening all module "methods" into
* a single namespace, I think you are losing a lot of clarity into what is actually happening, and
* trading it for a high level of decoupling.
*
* In simple cases this is probably preferrable, but let's say you have 30 or 40 different interactions
* that are all passing through the sub app as an event bus. I can see that tradeoff no longer being
* worth it.
*
* I think a more traditional service locator approach would solve this problem. So would DI, but
* I think service location fits better with marionette modules
*/
// quick and dirty service locator implementation
;(function(Marionette){
var services = {
store: {},
get: function(name){ return this.store[name] },
set: function(name, value){ this.store[name] = value }
}
_.bindAll(services)
Marionette.Application.prototype.service = services.set
Marionette.Application.prototype.service = services.get
}(Backbone.Marionette));
// in each of the different module files
app.service("contact-repository", ContactRepository)
app.service("notifier", Notifier)
app.service("some-module", Something)
// use case
module('contacts-deleting', function(){
// ask the sub app to find the services this current service needs to do its job
var repository = app.locate('contact-repository'),
notifier = app.locate('notifier'),
something = app.locate('something')
controller = {
delete: function(contact){
var condition = something.isConditionMet(contact)
$.when(condition).then(function(){
if(contact.foo && condition){
repository.destroy(contact)
} else {
notifier.error('Some Message')
}
})
}
}
app.service('DeletingContacts', controller)
})
/*
* I think the benefits are
*
* 1 - we are back more into a "interactions with other objects" world, rather then a "everything is like a private method world". This can make it easier to understand what role a given thing plays, just by looking at what its dependancies are.
* 2 - it can make it easier to find out who is using a given thing -- you only have to grep for who is asking for it, not every message it responds to
* 3 - it exposes a bit more of the structure of the dependancies, which can give you hints on where to go next when tracing the code in complex scenarios
*
* downsides
*
* 1 - it is a bit less decoupled (although still pretty damn decoupled), and you stop thinking about interactions within a sub app as a flat thing.
*
* 2- tests aren't quite as nice, you need to now pass in stubs rather then just test messaging. i think this could be a good thing too due to clarity, but it is definately more typing
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment