JavaScript Architectuur
Sinds AJAX is ‘uitgevonden’ (als je dat zo mag noemen) is het leven van de webdeveloper enigszins veranderd. Nieuwe verwachtingen op het gebied van front-end development leveren ook een nieuwe set van problemen. Verwachtingen die bijvoorbeeld door applicaties als Google Calender of Google Mail geschapen worden. Zo ook voor het project waar op dit moment de laatste hand aan gelegd wordt: een soort van gebiedsselectie tool voor een distributeur van ongeadresseerd reclamedrukwerk. Deze tool leunt hevig op de Google Maps API om de selecties te maken en te tekenen als lagen over de Google Maps kaart. Zie hiervoor ook eerdere blogposts op deze blog van o.a. Rob van de Meulengraaf. In deze blogpost wil ik me meer focussen op het architecturele design van de voorkant van de website in het algemeen.
Inleiding
Er zijn vele soorten AJAX applicaties/websites uiteraard. Van een website waar een enkel formulier ver-AJAX-d is, tot aan applicaties zoals Google Mail. Als we het hebben over uitdagingen dan is dat met name deze laatste categorie. Omdat het achterliggende idee van AJAX natuurlijk het updaten van stukjes content van een pagina is heeft het veel weg van ontwikkelen aan de hand van components zoals bijvoorbeeld in Swing applicaties.
Om een indruk te geven van wat ik bedoel wilde ik hieronder graag een paar screenshots van de applicatie laten zien waar ik aan gewerkt heb de afgelopen maanden. Helaas mogen deze screenshots nog niet worden vrijgegeven en zal ik het met een iets minder fancy voorbeeld moeten doen.
High level architecture
In een meer traditionele applicatie zal je je in het begin waarschijnlijk bezig houden met een soort van screenflow om te bekijken welke schermen je op welke manier kan bereiken en op welke links je daarvoor moet klikken. In deze applicatie gebeurd er veel meer op één scherm. In het geval van de genoemde applicatie wordt dit ook een beetje gedreven door het feit dat veel acties van toepassing zijn op de Google Maps kaart. Deze kaart is redelijk duur om te laden. Duur in bandbreedte, initialisatietijd en zelfs ook licentiekosten1. Vandaar dat er gekozen is om te werken met componenten die geupdate kunnen worden aan de hand van acties die op de server plaatsvinden. Acties zoals het wijzigen van een selectie op de kaart of het toevoegen van een editie bijvoorbeeld.
Feit is dat er op dit niveau meer nagedacht moet worden over hoe je JavaScript architectuur er uit moet komen te zien. Ad hoc stukjes JavaScript plaatsen is al heel snel niet meer te overzien. Al met al een behoorlijke uitdaging voor iemand die zo’n architectuur opzetten nog niet eerder ondernomen had, maar na wat brainstorm sessies met Lennaert en later ook Rikkert is er het volgende uitgekomen.

Ik wil in het kort de verschillende delen van dit diagram langsgaan en zeggen wat het doet. Omdat ik de applicatie (nog) niet als voorbeeld kan nemen heb ik zelf een kleine applicatie in elkaar gezet. Je kan deze hier downloaden. Ik ben helaas niet in de gelegenheid om op deze blog een ‘running example’ hiervan te laten zien maar er zijn een aantal mogelijkheden om het voorbeeld op je computer draaiend te krijgen2:
- Als je bekend bent met de Maven Eclipse Plugin kan je met een
mvn eclipse:eclipsein/js-blog/js-blog-mavende Eclipse WTP project files genereren. Hierna kan je het in Eclipse importeren (File -> Import) en aan je WTP server toevoegen. - Installeer het project in je locale repository door middel van een
mvn installin/js-blog/js-blog-maven. Ga hierna naar de directory/js-blog/js-blog-siteen doe eenmvn jetty:run. Surf hierna naar http://localhost/js-blog-site/ om het voorbeeld te zien. - Je kan ook de WAR in een bestaande applicatieserver of servlet container droppen.

Het voorbeeld bestaat uit 3 componenten. Een voor input (een simpele button) en twee voor output (grafiek en tabel). Als we de componenten langsgaan zullen we zien hoe deze simpele applicatie werkt en hoe de verschillende lagen met elkaar communiceren.
Application
Dit is het component dat al de ‘wiring’ van de applicatie doet. Hiermee bedoel ik dat het de verschillende type componenten instantieerd en aan elkaar hangt. Zo krijgen de controllers een referentie naar het UIComponent mee wat ze controllen, maar ook een referentie naar de ServiceProvider om hiervan gebruik te kunnen maken.
com.finalist.jsblog.Application = (function() { // imports var Event = YAHOO.util.Event; var Dom = YAHOO.util.Dom; /** * <p>Application, initializes the application: wires components and * controllers and offers services.</p> * * @class Application * @namespace com.finalist.jsblog * @constructor */ function Application() { this.viewModel = new com.finalist.jsblog.model.ViewModel(); this.components = {}; this.controllers = {}; this.initialize(); } Application.prototype = new YAHOO.util.EventProvider(); Application.prototype.initialize = function() { // serviceProvider this.serviceProvider = new com.finalist.jsblog.service.ServiceProvider(this.viewModel); // components this.components.button = new com.finalist.jsblog.component.Button("get-data-button"); this.components.chart = new YAHOO.widget.StackedColumnChart("chart", new YAHOO.util.DataSource([]), {}); this.components.datatable = new YAHOO.widget.DataTable("datatable", [ {key: "month", label: "Maand"}, {key: "rent", label: "Huur"}, {key: "utilities", label: "Gas/Water/Electra"} ], new YAHOO.util.LocalDataSource([]) ); // controllers this.controllers = { buttonController: new com.finalist.jsblog.controller.ButtonController(this.components.button, this.viewModel, this.serviceProvider), chartController: new com.finalist.jsblog.controller.ChartController(this.components.chart, this.viewModel), chartController: new com.finalist.jsblog.controller.DataTableController(this.components.datatable, this.viewModel) }; }; return Application; }());
De Application is ook hetgene wat gestart wordt met een window.onload event:
YAHOO.util.Event.on(window, "load", function() { window.Application = new com.finalist.jsblog.Application(); });
ServiceProvider
ServiceProvider is het component dat de verschillende services beschikbaar stelt aan de rest van de applicatie. Het gaat hierbij om services die bijvoorbeeld data ophalen aan de serverkant, of iets veranderen aan de serverkant. In het voorbeeld gaat het hier om het ophalen van de data om de grafiek en de tabel te vullen.
ServiceProvider.prototype.refreshChartData = function (callback) { var self = this; JsonConnect.asyncRequest("GET", Constants.SERVICE_URLS.CHART_DATA, { success: function (o) { // synchronize model self.viewModel.setChartData(o.responseObject); if (callback && callback.success) { callback.success(); } }, failure: function (o) { self.onFailure(o, "Unable to refresh chart data! [ServiceProvider.refreshChartData()]"); } }, null); };
Er wordt in dit specifieke geval een asynchroon request gedaan naar een url die nieuwe data ophaalt en als JSON terugstuurd. De JSON wordt automatisch geparsed (zie JsonConnect.js) en in de responseObject variabele gezet. In de success handler van deze call wordt het model gesynchroniseerd met de nieuwe data. In dit geval is dat simpelweg door een setter aan te roepen van het ViewModel. Ik kom hier later nog op terug. Een eventuele callback handler die doorgegeven is bij aanroep van deze service wordt uitgevoerd.
ViewModel, Model
Het idee achter ViewModel en Model is dat het de staat representeert die op de server en client aanwezig is. Model zou met name de staat moeten representeren van het server model. Eigenlijk een zo simpel mogelijke versie daarvan, namelijk alles wat relevant is vanuit een UI perspectief. ViewModel is een (zelfbedachte) term om de staat te benoemen van alles wat er buiten model alsnog aan staat is. Denk hierbij aan dingen die je zou benoemen als ‘huidige ….’. Als je bijvoorbeeld in Google Calendar meerdere eigen calendars hebt zou de huidige geselecteerde in het ViewModel komen te staan. (View)Model is verder geïmplementeerd als ‘EventProvider’ in YUI termen. Dit betekend dat het events kan afvuren waar andere partijen naar kunnen luisteren.
/** * <p>Set the active chart data</p> * * @method setChartData * @param chartdata chartdata */ ViewModel.prototype.setChartData = function(chartdata) { // override current chartdata this.chartdata = chartdata; this.fireEvent(this.CE.CHART_DATA_UPDATED, {newData: this.chartdata}); };
Ik heb in het voorbeeld geval de scheiding niet zo strict genomen en alleen het ViewModel gebruikt en hier direct de ‘chartData’ op gezet in plaats van nog een Model binnen dit ViewModel aan te maken. Op het moment dat er iets in het view model veranderd wordt er een ‘custom event’ afgevuurd. Dit is een custom event wat gedefinieerd is in de constructor en voorzien is van een ‘public’ constant CE.CHART_DATA_UPDATED. Bij het zetten van nieuwe chartdata in het model wordt er dus een event afgevuurd die als parameter de nieuwe data meegeeft.
UIComponents
De UIComponents zijn de componenten waar uiteindelijk de in- en output van de applicatie mee geregeld is. In het voorbeeld gaat het hier om twee output componenten (de grafiek en de tabel) en een input component, de button. Vooral de button is nogal stupide als ‘component’ maar voor het voorbeeld wel toerijkend.
Uiteindelijk zijn het de UIComponents die zorgen voor de interactie met de gebruiker. Zij luisteren naar DOM events (mouseover, click, keyup, …) en bepalen of dat een specifieke actie voor hun component is. Ook deze componenten zijn ‘EventProviders’ en in het geval dat een DOM event een event is wat van toepassing is op dat component vuren ze een meer specifiek custom event af.
com.finalist.jsblog.component.Button = function() { // 'imports' var Dom = YAHOO.util.Dom; var Event = YAHOO.util.Event; var Selector = YAHOO.util.Selector; var Lang = YAHOO.lang; // element constants var ELEM_ELEMENT = "element"; /** * <p>Button component, used to display progress information.</p> * * @class Button * @namespace com.finalist.component * @constructor */ var Button = function(element, options) { Button.superclass.constructor.call(this, element, options); // register events this component can fire this.CE_BUTTON_CLICKED = "button-clicked"; this.createEvent(this.CE_BUTTON_CLICKED); }; Lang.extend(Button, YAHOO.util.Element, { init: function () { var self = this; Button.superclass.init.apply(this, arguments); var button = this.get(ELEM_ELEMENT); Event.on(button, "click", function() { // fire component custom event self.fireEvent(self.CE_BUTTON_CLICKED); }); }, initAttributes: function (options) { Button.superclass.initAttributes.call(this, options); }, destroy: function () { } }); return Button; }();
Zoals gezegd is dit een behoorlijk dom component. Het luisterd naar een DOM click event en gooit vervolgens een custom event ‘CE_BUTTON_CLICKED’. Controllers kunnen op dit event inhaken en hier vervolgens wat mee doen. In dit geval is het opvangen van een ‘click’ en het gooien van een ‘click’ niet zo heel erg interessant, maar je kan je voorstellen dat het ‘Other Calendars’ componentje in Google Calendar bij het klikken van een van de andere kalenders een CE_CALENDAR_DESELECTED afvuurt met wat meer informatie voor de controller. Een DOM click wordt dan dus een event met wat meer betekenis. Het is overigens niet zo dat een component alleen maar input of alleen maar output behelst uiteraard. Beide kunnen het geval zijn.
(Component)Controllers
Als laatste hebben we de controllers van de verschillende UIComponenten. Ze registreren zich op Events (waar ze interesse in hebben) van het ViewModel en van het UIComponent wat ze bedienen. De ButtonController bijvoorbeeld is geinteresseerd in de CE_BUTTON_CLICKED van het button component:
ButtonController.prototype.initialize = function() { var self = this; // subscribe to events of the compontent you are interested in. this.button.subscribe(this.button.CE_BUTTON_CLICKED, function _doButtonClickCallBack() { self.onButtonClick(); }); }; ButtonController.prototype.onButtonClick = function() { this.serviceProvider.refreshChartData(); }
Via een subscribed event van het Button component wordt dus de ServiceProvider.refreshChartData() aangeroepen (zonder expliciete callback functie overigens). Zoals we zagen in de ServiceProvider levert dit een synchronizatie op van het view model (namelijk de chartData).
Andere controllers zoals de ChartController en de DataTableController zijn juist geinteresseerd in de informatie vanuit het view model en registeren zich daarom op die events:
DataTableController.prototype.initialize = function() { var self = this; // reset the datasource so we can alter it from this controller this.datatable.set("dataSource", this.datasource); // subscribe to events we are interested in! this.viewModel.subscribe(this.viewModel.CE.CHART_DATA_UPDATED, function(e) { self.onChartDataUpdated(e.newData); }); }; DataTableController.prototype.onChartDataUpdated = function(data) { this.datasource.liveData = data; this.datasource.sendRequest(null, { success: this.datatable.onDataReturnInitializeTable }, this.datatable); };
ChartController.prototype.initialize = function() { var self = this; // reset the datasource so we can alter it from this controller this.chart.set("dataSource", this.datasource); // set the series definition this.chart.set("xField", "month"); var seriesDef = [ { displayName: "Rent", yField: "rent" }, { displayName: "Utilities", yField: "utilities" } ]; this.chart.set("series", seriesDef); // subscribe to events we are interested in! this.viewModel.subscribe(this.viewModel.CE.CHART_DATA_UPDATED, function(e) { self.onChartDataUpdated(e.newData); }); }; ChartController.prototype.onChartDataUpdated = function(data) { this.datasource.liveData = data; this.chart.refreshData(); };
Het vergt enige kennis van hoe de YUI componenten werken, maar het basisidee is wel duidelijk denk ik. Zij luisteren naar het ViewModel.CE.CHART_DATA_UPDATED Event en updaten zich vervolgens aan de hand daarvan.
Overigens is het niet zo dat alle acties via de ServiceProvider hoeven te lopen. Neem als voorbeeld het switchen van actieve kalender en Google Calendar nog maar eens. Als je klikt op een andere kalender kan je je voorstellen dat het component bijvoorbeeld een ACTIVE_CALENDAR_CHANGED event gooit. Deze wordt door de controller vervolgens afgevangen en direct op het view model gezet. Andere componenten die luisteren naar het view model en ‘horen’ dat er een andere actieve kalendar is kunnen zichzelf vervolgens updaten (bijvoorbeeld het hertekenen van het week overzicht).
Terugkijkend op het project
Terugkijkend op wat het resultaat geworden is van de applicatie ben ik behoorlijk tevreden over deze architectuur. Het kostte wat moeite om ermee te starten (ook omdat onze YUI kennis op vakantie was
) en een deel van de applicatie (prototype) moest worden gerefactord om hiermee aan de slag te gaan. Maar over het algemeen genomen werkte het cirkeltje Component -> Controller -> ServiceProvider -> Model -> Controller -> Component behoorlijk goed. Het event mechanisme maakt het zeer gemakkelijk om componenten onafhankelijk van elkaar te instantiëren (je kunt ‘subscriben’ op events die nog niet bestaan) en het uitbreiden van een controller aan de hand van een nieuw event was uiteindelijk redelijk makkelijk.
Met name de ServiceProvider was handig om een enkel punt te hebben waar (business) AJAX calls konden werden gedaan en er zodoende ook een enkel punt was om een AJAX failure afhandeling te doen.
Er moet wel gezegd worden dat dit een ideaal plaatje is van de project-werkelijkheid. Voortschreidend inzicht wil niet altijd zeggen dat je ook tijd hebt om alles op de schop te nemen helaas.
Conclusie
In deze blogpost heb ik geprobeerd uiteen te zetten wat we aan JavaScript architectuur hebben bedacht voor mijn lopende project. Ik ben zelf wel tevreden over wat er uiteindelijk is neergezet. Het is gestructureerd en behoorlijk onderhoudbaar – al zal het wat tijd kosten om in te lezen. Then again, dat zal alleen de toekomst leren (voor diegenen die er nog mee aan de slag gaan), als je er nog zo diep inzit lijkt alles best wel onderhoudbaar. Time will tell.
Een ding houdt me nog een beetje bezig en moet nog even gezegd worden. Omdat er al een behoorlijk uitgebreid prototype lag op basis van YUI hebben we gekozen voor YUI en een eigen JS structuur. Ik ben wel benieuwd in hoeverre GWT ons had kunnen helpen bij dit probleem (als we van scratch waren begonnen).
1Er wordt in het licentiemodel van Google voor Premier Maps gerekend met ‘sessies’ deze zijn gedefinieerd als het één keer het laden van de Google Maps API javascript file.
2Zorg in ieder geval dat je maven 2.x werkend hebt.

Ik ben het toch hier en daar met je oneens. Ik zal even wat dingen aanhalen.
Tight coupling.
Je service provider kent je viewModel, je ButtonController kent je serviceprovider, je DataTableController kent het feit dat er een chart is (maar niet de chart zelf) en je Applicatie kent al die dingen. Dit is behoorlijk tightly coupled. Als je bijvoorbeeld je chart zou vervangen of eruit gooien, moet je in een hoop plaatsen vanalles veranderd. Je refereert zelfs aan een cirkeltje Component -> Controller -> ServiceProvider -> Model -> Controller -> Component. Dat cirkeltje zou er niet moeten zijn.
Controllers
Je controllers zijn zeer sterk gericht op een technische groepering, je schrijft de controllers op de componenten. In mijn ervaring is het handiger om controllers te schrijven op functionele modules. De button update de chart, dus de chart en de button (en wellicht andere componenten die met de kaart te maken hebben, zoals bijvoorbeeld waarde inspectie) kunnen worden samengevoegd in één controller / module. Deze controller kan ook zorgen voor instantiering van componenten / widgets. De applicatie hoeft helemaal niet geinteresseerd te zijn in welke widgets een module gebruikt.
YUI
Als er één onderdeel is wat extreem tightly coupled is, is het wel YUI, in zowat elke library staan aantal regels om Dom en Event imports te doen. Vaak heb je maar een onderdeel van de functionaliteiten nodig. Door deze op een lager niveau te wrappen, zit je framework niet zo vast aan je applicatie. Dat maakt het makkelijker om bijvoorbeeld een upgrade te doen naar YUI 3 of om te schakelen naar JQuery ofzo.
Als je je applicatie opdeelt in een aantal lagen, is het belangrijk dat die lagen niet van elkaar afhankelijk zijn, dat bereik je door, zoals je ook al aangeeft, events of notifiers. Deze lopen in principe altijd van en naar de application core, niet direct tussen componenten. De (event) flow in jouw voorbeeld applicatie zou dan als volgt zijn:
DOM -> Button component -> chart controller -> application
Vervolgens bedenkt de applicatie dat ie data nodig heeft, dat via een serviceprovider laten lopen is bijzonder nuttig:
application -> serviceprovider -> http -> server -> http -> serviceprovider -> application
nu heeft de application nieuwe data, wat weer doorgegeven kan worden naar iedereen die wil luisteren dat de data is geupdate (de tabel bijvoorbeeld ook)
application -> chartcontroller -> chart
application -> datatablecontroller -> table
Om je application de data “in bedwang” te laten houden, kan je een model aan de applicatie koppelen. Dit model moet uiteraard niet bereikbaar zijn voor modules. De balans tussen client model en servermodel is vaak een beetje uitzoeken, houdt niet teveel data op de client in memory, dat maakt de boel traag. Teveel op de server maakt de boel overigens ook traag. Het is per applicatie uitzoeken waar het optimum ligt.
Om de applicatie (en model) af te schermen van de modules wordt er soms nog een laag tussen gezet, die wel commander, sandbox of broker wordt genoemd. Deze zorgt in essentie voor de communicatie (via events) tussen applicatie en modules. Dit heeft als voordeel dat zelfs de applicatie core kan worden vervangen zonder dat je je modules hoeft aan te passen. Handig voor hergebruik. Dit een enigszins vergelijkbaar met wat je hier ViewModel noemt.
De commando’s die de applicatie beschikbaar heeft zijn heel high level en komen op een ontwerpvlak behoorlijk dicht tegen use cases aan. In het geval van je chart applicatie zou een use case kunnen zijn “update data”. Met de use cases in het achterhoofd wordt de commander ontworpen. In het kader van hergebruik kunnen modules varieren in hoeveel ze samenvoegen. Een voorbeeld van een module die ondertussen veel gebruikt wordt is de google maps. Hierin zitten allerlei individuele widgets die binnen het component samenwerken. Naar buiten is er een beperkte(re) api beschikbaar.
Een belangrijk punt is dus hier richten op functionele blokken en niet op technische samentrekkingen. Alle knoppen in je applicatie door één buttoncontroller laten besturen is geen goed idee. Als je namelijk een functionaliteit toevoegd dat (oa) een knop bevat, moet je in de buttoncontroller gaan zitten prutsen, evenals in een hele reeks andere controllers van widgets die met die functionaliteit te maken hebben. Schrijf liever een nieuwe controller/module voor die bepaalde functionaliteit. Als je commander volledig is geimplementeerd (in de zin dat alle use cases er in zitten), zou je hem zo in kunnen pluggen. Gaat het om nieuwe functionaliteit (met nieuwe use cases), dan wordt ook de commander uitgebreid.
Dit uitbreiden van de commander hoeft niet per se op die plek. JavaScript leent zicht prima om aspect georienteerd te werk te gaan. Je kan dus ook een soort “plugin” schrijven die functionaliteiten toevoegd aan de commander. Of dit nuttig is, hangt af van de grootte van je applicatie en de flexibiliteit die hij moet hebben
Rikkert October 1, 2009 17:51
… Ik ben het toch hier en daar met je oneens. Ik zal even wat dingen aanhalen.
Dat geeft niet. Het idee is ook een beetje te sparren over wat nou handig was en niet.
Ik weet niet of ik op alles kan reageren (zeker nu niet qua tijd) maar hier wat reactie:
Tight coupling
Ja de ServiceProvider kent het ViewModel, in mijn ogen wil ik juist de ‘synchronizatie’ van mijn server state richting (View)Model laten doen op een plek. En de DataTableController kent niet het feit dat er een chart is, althans dat is niet de bedoeling geweest maar ik begrijp dat ik geen handige naamgeving heb gebruikt. Wat ik natuurlijk op mijn model had moeten zetten was *RentData* of iets in die richting. Het idee is *juist* dat je de Chart of de Table of allebij weg kan gooien zonder dat je iets merkt. (En dat kan ook). Beide werken op zeg maar de data die ik nu ‘ChartData’ genoemd heb en wat natuurlijk niet zo had moeten zijn.
Ik refereer inderdaad aan het cirkeltje, maar dat wil niet zeggen dat dat cirkeltje niet op allerlei plekker doorgesneden kan worden zonder dat er iets mis gaat. Als een controller niet op een event uit ViewModel registreerd is er niets aan de hand. Dan ‘stopt’ het cirkeltje gewoon bij ViewModel. Ik wil het het cirkeltje niet impliceren dat er ook ‘wetenschap’ is in de richting van de pijl. Ik bedoel dus niet te zeggen dat als er staat Component -> Controller dat de Component van zijn controller weet (het is andersom, en Event gebaseerd). Ik bedoeldat dat dit de (data)flow was veelal met Events geregeld en dus loosly coupled.
Controllers:
Ik wilde ook hier niet helemaal de illusie wekken dat een enkel component altijd een controller heeft, of visa versa, maar voor het kleine voorbeeld wat ik gegeven heb is dat wel zo. Wel ben ik het met je eens dat er ruimte voor verbetering bij het instantieren van de hele boel is en misschien is het wel handiger om controllers de componenten te laten instantieren. … Ik weet niet aan de andere kant vond ik het toentertijd ook wel logisch klinken dat een component geinstantieerd kon worden (als UI) en dat je eventueel een controller kon maken waar je zijn referntie een meegaf i.p.v. andersom.
YUI:
Ik wil YUI benutten uiteraard, mijn componenten zijn gebaseerd op YUI. Het lijkt me totaal niet handig om tussen mijn framework en mijn componenten nog een eigen extra abstractielaag te maken om zodoende later nog eens naar jQuery over te stappen. Je kan natuurlijk abstraheren tot je een ons weegt, maar volgens mij doet dat geen goed. Klinkt een beetje als Hibernate abstraheren voor als je er nog eens een ander ORM framework wil gebruiken. Daar zie ik weinig reden voor.
Volgens mij hebben we het qua applicatie en en lagen in beginsel wel over hetzelfde. Alleen lopen mijn lijntjes juist naar mijn ViewModel en niet naar mijn Applicatie. Zoals ik aangaf is Application een wiring van componenten. Soort van Spring applicationContext configuratie die alle dingen instantieerd, wired en zich dan koest houdt. Jij zegt dat Application data nodig heeft en dat is precies wat ik doe alleen zet ik het op het (view)model (wat eigenlijk ‘in’ application zit). Het lijkt me juist wel dat modules van mijn model weten, dat is toch juist waar ze op moeten reageren?
Volgens mij is wat jij commander / broker noemt hetgene wat ik ongeveer bedoel met ServiceProvider. De ServiceProvider is niet zozeer alleen bedoelt als HTTP-access, maar juist om de ‘use-case functies’ te ontsluiten. De ServiceProvider is ook slimmer dan alleen maar HTTP-accessor in dit geval, hij heeft namelijk weet van het feit dat er na deze actie (bv getData()) de data van het ViewModel geupdate moet worden. Een andere use case om de huidige gemiddelde huizenprijs op te vragen zou hier ook in komen (en zou ws een andere waarde in mijn viewmodel updaten.)
Ik snap je punt over functionele blokken en ben dit met je eens. Ik wilde niet insinueren dat een component altijd een eigen controller heeft en zeker niet dat een *type* component (button) een eigen controller heeft, dat is geen goed idee. Verder is het ongetwijfeld zo dat de grootte van de applicatie invloed heeft op het feit of er wel of niet nog een soort van Modules moeten komen en hierbij weer gekeken moeten worden wat er per module ge-encapsuleerd zou moeten worden.
Bedankt voor je comment zitten een hoop dingen in die ik wel bedoelde maar niet goed had opgeschreven en uberhaupt is het nuttige informatie.
Auke van Leeuwen October 1, 2009 18:55