Alles wat met programmeren, coden & scripting te maken heeft.

JS to JS compiler

Door Gamebuster op woensdag 8 oktober 2014 23:01 - Reacties (8)
Categorie: if(post.relatedTo("programming")), Views: 1.781

Na vele to-JS compilers bekeken, ben ik zelf ook gaan experimenteren met de ontwikkeling van een taal die naar Javascript compileert.

Niet omdat Javascript slecht is, maar omdat het schrijven van "nette" javascript er automatisch voor zorgt dat je codebase groter en slecht comprimeerbaar (minify-baar) wordt. Geen Javascript minifier kan object property names "slim" "obfuscaten". Ik zoek dus een manier om dit wel te realiseren.

Lang verhaal zal ik jullie besparen; een voorbeeld zegt meer dan 1000 woorden:

De "Super" Javascript:

JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// dom.sjs
export namespace dom {
  function ready(fn) {
    extern window.addEventListener("DOMContentReady", ->{ fn(); });
  }
}

// main.sjs
import "dom";
using namespace dom;

ready(->{
  if(extern window.location.pathname == "/")
    extern console.log("You are at the root page!");
});

if(dom::ready == ready)
  extern console.log("namespaced!");



Resulterende Javascript (geannoteerd)

JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// The compiled result of the SJS file. Variable names are not obfuscated for the purpose of this
// demonstration, but they might as well have been. If wrapped in (function() { ... })(), any JS
// minifier will obfuscate and minify the code effectively.

// All extern references. Window is used in multiple extern references, so it is wrapped in a
// variable as well. The extern keyword declares external variables to be imported and used in our
// script.
var _Ewindow = window,
    _Ewindow_Elocation_Epathname = _Ewindow.location.pathname,
    _Econsole_Elog = console.log;

//   import "dom";
//   using namespace dom;
// The import and using statements don't produce any code.
// The function defined in the 'dom' namespace is inlined later since it is only used once.


//   ready(->{
//     if(extern window.location.pathname == "/")
//       extern console.log("You are at the root page!");
//   });
// Here, we use the ready-function with a function argument.
// Since the ready-function is only called once, the function is inlined.

// Extern window.addEventListener is only used once, so it is inlined and not declared at the top.
_Ewindow.addEventListener("DOMContentReady", function() {
  if(_Ewindow_Elocation_Epathname == "/")
    _Econsole_Elog("You are at the root page!");
});

//   if(dom::ready == ready)
//     extern console.log("namespaced!");
// since dom::ready and ready are the same thing (ready implicitly namespaced by the 'use namespace'
// statement) and there is no possible conflict for another ready-function, the comparison always
// equals to 'true'. Therefore, the content of the if-statement can be unwrapped and the
// if-statement removed.
// The extern console.log is stored in a variable at the top of the script, because it is referenced
// as external multiple times.
_Econsole_Elog("namespaced!");



Minified resultaat: (beautified)

JavaScript:
1
2
3
4
5
6
7
8
9
var n=window,
  e=n.location.pathname,
  o=console.log;

n.addEventListener("DOMContentReady", function(){
  "/" == e && o("You are at the root page!")
}),

o("namespaced!")



Besef dat dit theoretisch is. Ik heb geen werkende cross-compiler. Ik ben aan het fantaseren hoe mijn eventuele cross-compiler zijn werk zou doen. Het uiteindelijke doel is modulaire javascript die hevig "comprimeerbaar" is voor een zo compact mogelijk resulterend javascript bestand voor de client, zoals je browser.

In een wereld met megabit verbindingen, is dit nog relevant?
Ik vind van wel. Code-bases groeien hard clientside, waarbij sommige sites meer dan een megabyte aan javascript hebben. Dit vertraagt het laden van de site dramatisch en kost alleen maar onnodig bandbreedte voor zowel jou en mij.

Is een eigen taal ontwikkelen niet een idiote taak?
Ik heb ervaring met het gebruik van parser generators voor "eigen talen". Tevens heb ik een grammar file gevonden die 100% ECMA6 compatible is. Mijn plan is om deze grammer file aan te passen met mijn features en daarmee de "super javascript" te tokenizen en parsen.

Vervolgens moeten er diverse optimalisatie tools en andere scripts op los gelaten worden om de code te transformeren naar "normale" ECMA5-compatible javascript.

Het is veel werk, maar zeker niet onmogelijk.

Javascript is een kut taal. Een taal die naar JS compileert kan JS's gebreken hooguit verstoppen, niet weghalen
Javascript kan een geinige taal zijn. Het heeft zijn sterke punten. Het is ook zeker niet mijn intentie Javascript "features"/gebreken te verbergen. Ik wil dat de taal aanvoelt als javascript, inclusief al zijn gebreken en rare features. Ik wil enkel wat extensies voor het makkelijker maken van bepaalde taken, zoals prototype inheritance en property management (defineProperty enz.): Ik wil geen neppe "Class-oriented taal" ervan maken.

Groeperen op wat het doet of wat het is?

Door Gamebuster op woensdag 28 mei 2014 18:24 - Reacties (14)
Categorie: if(post.relatedTo("programming")), Views: 4.287

Bij grotere software pakketten groepeer je je code. Of je nou in C, Java, Ruby of Haskell werkt: Groeperen van code doen we allemaal. Voor ieder project waar je wat langer aan verwacht te werken, verschijnen al gauw meerdere bestanden. Voor nog grotere projecten verschijnen de mapjes. Bij frameworks wordt de mappen-structuur zelfs voor je afgedwongen of gegenereerd. Maar wat is eigenlijk de “juiste” manier?

Groeperen op wat het is
Is het een model? Een entity? Een controller? Een view? Bij frameworks als Rails zie je vaak een mappen-structuur waarbij alle modellen onder de “models” map vallen, alle controllers onder de “controllers” map vallen en alle views onder “views”.

Zo heb je alles netjes bij elkaar. Wil je een view wijzigen? Prima, open de views map en leef je uit. Wil je je model wijzigen? Prima, open de models map. Klinkt niet verkeerd toch?

Stel, ik heb een Twitter applicatie gebouwd in een MVC framework als Rails. De modellen User en Tweet zitten in de models map, de controllers hiervoor in de controllers map en de views voor alle acties voor de controllers hiervoor zitten in de views map.

Nu wil je een veld toevoegen aan User. Je wil op iedere profielpagina een link toevoegen naar de website van de gebruiker; deze link kan de gebruiker zelf toevoegen.

Om dit te doen, moet je eerst de User model aanpassen: Je moet een homepage veld toevoegen. Je opent de models-map en voegt het veld toe. Nu moet je toevoegen dat de gebruiker dit veld kan invullen en bewerken. Je hebt al een gebruiker bewerken controller actie en view, dus je hoeft deze 2 alleen nog aan te passen. Je opent de controllers map en past de controller aan met een default value en een assignment van de input params naar het model. Verder open je de views map en pas je de view aan zodat er een veld zichtbaar wordt.

Nou is dit een vrij simpel voorbeeld. Complexer voorbeeld is een test driven applicatie. Al gauw heb je vele mapjes open in je file drawer. Je wijzigingen in de applicatie zijn verspreid over een wijd arsenaal aan mappen.

Groeperen op waarvoor het is
Stel nou dat je alles groepeert op wat het is. Je hebt een users-map met daarin de user-model, de users controller en alle views voor users. In dezelfde map zitten zelfs je tests, want we schrijven natuurlijk wel tests voor de software die we maken, toch?

Voor de bovenstaande aanpassing, hoeven we maar 1 map te openen: users. Daaronder zitten alle bestanden die we moeten wijzigen. De tests, de controller, het model en de views.

Alle wijzigingen zijn gegroepeerd onder 1 map. Je hebt niet hoeven zoeken naar alle bestanden die je nodig hebt. In dit is het direct duidelijk dat je alleen dingen hebt veranderd onder /users.

Wat vind jij?
Tot nu toe heb ik altijd gegroepeerd op wat het is, simpelweg omdat dat “the way to go” is bij frameworks als Rails, maar mijn recente loskoppeling van Rails heeft me aan het denken gezet of dat wel de meest efficiŽnte manier is voor mij.

Ik denk dat het gezond is je primaire mappen-structuur meer te focussen op wat de applicatie inhoudt (tweets, users) en niet wat er allemaal in zit (models, controllers, views)

Dus... Groeperen op wat het is, of waarvoor het is?

Ruby on Rails: offtrack

Door Gamebuster op zaterdag 17 mei 2014 23:55 - Reacties (10)
Categorie: if(post.relatedTo("programming")), Views: 3.950

Na 3 jaar fulltime programmeren met Ruby on Rails heb ik besloten af te stappen van het framework.

Waarom? Hierom:

Plugins & Dependencies
Rails met de vele gems eromheen hebben naar mijn idee een te groot black box gehalte. Bestaande code en frameworks gebruiken is gebruikelijk een goed idee. In Rails projecten heb je echter gauw te maken met tientallen "eigen" dependencies (zoals opgegeven in je Gemfile) en 100en dependencies van de gems die je hebt ingeladen.

Natuurlijk ben je niet verplicht die gems te gebruiken. Maar waarom zou je het zelf maken als je een gem kan pakken die ongeveer doet wat je wil doen? Dit gaat gewoonlijk prima en je hebt snel resultaat.

Echter, nu blijkt na een jaar stabiel draaien en geen onderhoud dat er opeens wat moet veranderen... Oeps! De documentatie van oude versie van je gem is nergens meer te vinden; of de DSL van de nieuwe versie is compleet anders; of de nieuwe versie werkt niet meer samen met de nieuwe versie van een andere gem die ervan afhankelijk was.

Je zit dus vast aan die oude versie van die gem. Een heel netwerk aan gems kan je nu ook niet updaten zonder samenhangende gems weer te updaten, die weer hangen aan andere gems. In het ergste geval kan je zelfs je Ruby niet updaten, omdat er een enkele wazige feature uit een gem afhankelijk is van een wazige bug van een bepaald patchlevel van een bepaalde ruby. In sommige extreme gevallen start de hele applicatie niet meer en ben je uren of zelfs dagen bezig om je development omgeving weer draaiende te krijgen.

Je applicatie is geweven door deze dependencies. Je zit vast aan de gems. Devise, Cancan, MetaSearch, WillPaginate, enz. Je nieuwe feature vereist een stukje functionaliteit die al zit in een nieuwe versie van gem X, maar gem X is afhankelijk van gem Y. Je moet dus nu de functionaliteit van de nieuwe versie van gem X namaken of de oude versie zelf hotpatchen naar de nieuwe functionaliteit.

Voor je het doorhebt zijn alle dependencies leidend voor je applicatie en wordt je naar de grond getrokken om Łberhaupt de applicatie draaiende te houden.

Oplossing: Vermijd het gebruik van gems in kritieke delen van je applicatie...

Rails is je applicatie
Heb je een eigen Rails project? Denk mee met de volgende stelling:

Stel dat je de Rails gem, en al zijn bijbehoren componenten, zou verwijderen uit je project, welk deel van het project werkt dan nog WEL? Zeer waarschijnlijk kan je je project niet meer starten, maar dat is niet zo erg. Draaien de unit tests voor je domain logic nog, of heb je al je domain logic in Models of Controllers verwerkt? Doet je applicatie het zonder database? Stel dat je je database zou vervangen door een klein Ruby object die alles in-memory opslaat, werkt je applicatie dan nog, of moet je al je domain entities omschrijven zodat ze niet meer ActiveRecord::Base als basis hebben?

In de meeste gevallen is dat niet zo. Is het bij jouw project wel zo? Goed bezig!

In de Rails projecten die ik heb gezien, is Rails de leidende informatie over het project; Het feit dat het een "Rails project" genoemd kan worden, zegt al genoeg: Dat je "Rails" gebruikt als framework moet geen leidend feit zijn van je applicatie. Je moet rails uit je project kunnen verwijderen en alle functionaliteit die niet relevant is voor de presentatie voor het web zou moeten blijven werken.

Rails is hiervoor niet ontworpen. Rails is ontworpen leidend te zijn in je applicatie. Als rapid prototype is dit prima, maar als long-living applicatie is dit niet wat je wil.

...
Ik kan heel lang doortypen wat mijn kritiek is op Rails. Het heeft zo z'n goede kanten, maar de gehele MVC structuur zoals bij moderne web frameworks toegepast wordt, spreekt mij niet totaal aan. Views in Rails zijn ook niet om te genieten. Ik vind ERB verschrikkelijk, net als templating languages, HAML, enz.

Maar wat dan wel?

Ben ik nog niet volledig over uit. Ik wil alle features die ik gebruik van Rails herzien en kijken of ik het wil blijven gebruiken. Mijn huidige grootste project is een nieuw project die nu als rapid prototype is opgezet als Rails project. De business logic is echter groter dan het rails project; Het rails project is slechts een dunne presentatie laag over de core van de applicatie.

De code van de applicatie is nu geschreven als een gem, volledig los van elke dependency (pure Ruby en core libraries). De enige keren dat ik 3rd party libraries gebruik, is wanneer ik de functionaliteit nodig heb in een bepaalde module. De gehele 3rd party library wordt verborgen gehouden voor de rest van de applicatie, net zoals dat hoort.

Ik wil mijn views graag opbouwen in de vorm van modules, net zoals je bij AWT of Swing doet in Java. In realiteit zal dit betekenen dat mijn view "blokken" bestaan uit een grote hoeveelheid objecten die HTML genereren. Ik kan dan standaard view modules gebruiken (die ik gewoon kan recyclen tussen alle projecten die ik heb) en bijzondere functionaliteit kan ik als subclasses realiseren. Dit lijkt me lastig te realiseren, maar o wat zou ik het graag willen realiseren. Ik heb het een keer geprobeerd tot een zeker niveau en het beviel me goed.

Hoe dan ook, ik rond deze blog af. Film kijken, blog schrijven en late uren na drukke dag vormen een slechte combinatie.

Prototyping: De verborgen kracht van Javascript

Door Gamebuster op vrijdag 4 april 2014 14:07 - Reacties (12)
Categorie: if(post.relatedTo("programming")), Views: 2.978

Javascript wordt vaak gezien als dat gedrocht waarmee gewerkt moet worden. Toch kan het een leuk taaltje zijn.

Je hebt vast wel eens iets gehoord als "Javascript isn't an Object Oriented language, but a prototype-based language". Maar wat houdt dat eigenlijk in? Wat is een prototype-based taal en hoe is het verschillend van een reguliere OO taal?

Regulier OO: Classes
In bijv. Java werk je met classes. Een class is een soort blueprint van de werking van een object. Je begint met het ontwerpen van een Car class en je geeft deze de benodigde functionaliteit: Start, Stop, VersnellingOmhoog, VersnellingOmlaag, Rem, Gas, enz. Eenmaal ontworpen heb je echter nog geen auto; je hebt enkel de plannen liggen die omschrijven hoe een auto moet zijn.

Met `new Car` wordt de 1e auto gemaakt. Dit herhaal je tot je de gewenste hoeveelheid auto's hebt.

Classes
Het realiseren van classes in Javascript is geen bijzondere bezigheid. Een beetje ervaren javascript ontwikkelaar is bekend met dit idee:


JavaScript:
1
2
3
4
5
6
7
function Car() {
  this.running = false;
}

Car.prototype.start = function() { this.running = true; };
Car.prototype.stop = function() { this.running = false; };
Car.prototype.getFuelPercentage = function() { return 1.0; };



Alhoewel dit prima werkt in Javascript, is deze manier niet altijd even netjes. Zeker wanneer je subclasses wil toevoegen zit je met een hoop magic die niet altijd zichzelf goed omschrijft.

Dit laat Javascript vies aanvoelen en bepaalde "magic" vergeten of niet toepassen resulteert niet altijd direct tot duidelijke fouten of bugs: Die komen vaak later pas boven water. Het alternatief is het gebruik van een library of framework voor het beheren van je classes, maar dat is ook verre van ideaal.

Prototyping
Prototyping in Javascript werkt anders dan gebruikelijk. Als je een auto wil ontwikkelen begin je niet met een blueprint, maar met een prototype. Je begint dus meteen met het bouwen van je eerste auto, precies zoals je hem wil hebben, met het idee dat je er later meer gaat produceren; meer auto's die exact hetzelfde zijn als die je nu ontwerpt.

Eenmaal je auto prototype werkend, dan kan je er een kopie van maken. Dit blijf je doen tot je je gewenste hoeveelheid auto's hebt. Ook kan je kopieen van kopieen maken. Zo kan je een kopie van een auto prototype maken, deze een andere kleur geven en daar weer een nieuw kopie van maken.

Zo werkt Prototyping in Javascript inhoudelijk ook, en eenmaal gewend werkt het een stuk prettiger en intuÔtiever dan je denkt:


JavaScript:
1
2
3
4
5
6
var car = {
  running: false,
  start: function() { this.running = true; },
  stop: function() { this.running = false; },
  get fuelPercentage() { return 1.0; }
};



Dit is je prototype. Merk op hoe compact en "clean" de definitie ervan is vergeleken de class definitie van daarboven. Ook kan je je prototype direct gebruiken:


JavaScript:
1
2
3
4
car.start();
car.running; // true
car.stop();
car.running; // false



Om een 2e auto te maken, maak je een kopie van je bestaande auto. Technisch zien, in Javascript, maak je een nieuw object met je auto als prototype:


JavaScript:
1
2
3
4
5
var myCar = Object.create(car);
myCar.running; // false
myCar.start();
myCar.running; // true
car.running; // false



Functionaliteit uitbreiden met "subclasses" in prototyping
Nu wil ik een auto prototype toevoegen die op diesel rijdt. Ik neem een normale auto en geef deze een dieselmotor. In de code implementeer ik alleen een nieuwe fuelPercentage getter die berekend op de hoeveelheid diesel aanwezig met de benodigde properties.

Merk op dat de syntax (helaas) wat complexer is dan het maken van een nieuw object.


JavaScript:
1
2
3
4
5
6
7
8
9
var dieselCar = Object.create(car, {
  dieselAmount: { value: 0 },
  dieselCapacity: { value: 100 },
  fuelPercentage: { get: function() { return this.dieselAmount / this.dieselCapacity; } }
});

dieselCar.running; // false
dieselCar.start();
dieselCar.running; // true



Ook hier kunnen we weer nieuwe dieselCars van maken met `Object.create`:


JavaScript:
1
myDieselCar = Object.create(dieselCar);



Tot slot kan ik nog steeds methodes en properties toevoegen aan de gehele prototype chain van de auto's, welke daarna van toepassing is op alle auto's die erop gebaseerd zijn:


JavaScript:
1
2
3
4
5
6
7
8
car.color = "black";
myDieselCar.color; // black
myDieselCar.color = "red";
car.color; // black
myDieselCar.color; // red
car.color = "blue";
dieselCar.color; // blue
myDieselCar.color // red



De logica hierachter is dat alles wat je assign't aan je object heeft prioriteit over alles in de prototype chain. Bij het aanroepen van myDieselCar.color wordt dan bijv. gekeken naar:

myDieselCar.color => dieselCar.color => car.color

De 1e gedefinieerde `color` die gevonden wordt, wordt geretourneerd. Prototyping brengt veel meer unieke en bijzondere eigenschappen en features met zich mee. Experimenteer ermee en doe research voor meer informatie. Probeer bestaande princiepes van class-based OO uit je hoofd te zetten als je met Javascript werkt, en je kan zowaar wat plezier hebben bij het schrijven van Javascript.

Gotcha's
Zet de klassieke OOP kennis uit je hoofd. Hou het bij duck typing. Waarom? Zie:


JavaScript:
1
2
3
4
5
6
7
myCar instanceof Object // true
myCar instanceof car // Error
myCar == car // false
myCar.prototype == car // false
myCar.constructor == car // false
myCar.__proto__ == car // true
typeof myCar == 'object' // true

Mijn ontwikkelingen in webontwikkeling

Door Gamebuster op maandag 31 maart 2014 11:44 - Reacties (7)
Categorie: if(post.relatedTo("programming")), Views: 3.448

Al jaren werk ik met Ruby on Rails. Mijn relatie met Ruby on Rails was overweldigend en geweldig. Ruby is een leuke taal en Rails heeft unieke features die mij erg sterk aanspreken. Met geluk en liefde werkte ik met Ruby on Rails.

In mijn vrije tijd speelde ik met andere talen. Zo ben ik al een lange tijd bezig met de ontwikkeling van een kleine game in C en heb ik gespeeld met NodeJS. De ontwikkeling van die projecten verliep soepel. Ik moest veel meer zelf doen, maar ik werd daarbij beloond met uitzonderlijk hoge performance.

Ruby on Rails was niet langer meer de geweldige redder van alles. Ik begon een afkeer te krijgen tegen diverse quirks van Rails. Los daarvan, Ruby en Ruby on Rails zijn samen bijzonder traag. Het is een super framework om wat werk gedaan mee te krijgen, maar als je long-living applicaties hebt waarbij performance een rol speelt, wordt Ruby on Rails niet meer je redder maar dat ding dat je telkens in de weg staat.

"Dit moet anders kunnen" dacht ik bij mezelf. Ik begon te preken bij collega's hoe ik het allemaal wel beter wist en hoe het allemaal anders kon. Ik begon opstandig te worden richting Rails en ik verloor mijn liefde voor haar. Ik begon met het zoeken naar alternatieve manieren voor praktisch alles wat Rails Rails maakt, tot ik me besefte:

Ik wil iets anders. Ik wil niet langer meer moeite doen om haar te veranderen; Ik wil een nieuwe liefde.

Maar wat? Welke omgeving is volwassen genoeg om volledig Ruby on Rails te vervangen?

Ik begon rond te shoppen voor andere frameworks. Gezien mijn positieve ervaringen met C en NodeJS begon ik daarin te zoeken.

Ik wil geen web framework op C++, want dat vind ik een verschrikkelijke taal. C is een leuke taal, maar niet praktisch om een grote web-applicatie mee te maken. NodeJS blijft dan over.

NodeJS heeft al enkele keren in de afgelopen jaren mijn aandacht getrokken. Het heeft een enorme community, Javascript heb ik nooit een afkeur tegen gehad en NodeJS bevat genoeg bestaande oplossingen voor webontwikkeling. Ook niet onbelangrijk: NodeJS i.c.m. Google's v8 engine is gewoon erg snel.

Goed, NodeJS is dus de een aantrekkelijke optie. Hoe nu verder? Welke van de 100en frameworks ga ik gebruiken?

Voor NodeJS zijn 100en, als niet 1000en, frameworks om je ding te doen op het web. Allemaal uniek in features en allemaal met een verse kijk op webdevelopment. Ik heb echter geen klik kunnen vinden met welk framework dan ook.

Ik begon aan mezelf te twijfelen. Ben ik nou zo kieskeurig? Ben ik te moeilijk? Heb ik teveel eisen? "Uiteindelijk moet ik gewoon wat gedaan krijgen, ik kan niet alles zelf gaan maken" dacht ik, hoe graag ik het ook zou willen.

Ik dacht: "Weet je wat? Ik begin gewoon met mijn applicatie en zie later wel welk (web)framework ik ga gebruiken!"

Ik pakte een NodeJS test framework erbij en ging m.b.v. TDD aan de slag met mijn applicatie volgens de patterns omschreven door deze kerel:

https://www.youtube.com/watch?v=WpkDN78P884

Ik begon met het aanmaken van entities (domme datastructuren; vergelijkbaar met modellen), interactions (single-responsibility controllers) en een test repository (een ding voor het opslaan en ophalen van entities)

NodeJS performance wordt bepaald door alle I/O en zware taken asynchroon uit te voeren. Omdat een repository data "persistent" zou moeten opslaan, zou-ie daarvoor I/O operaties moeten uitvoeren. De daadwerkelijke implementatie van de repository interesseert me niet: MySQL, MongoDB, plain text files, CSV files, Excel files of zelfs in-memory: het zal me een worst wezen. Ik weet wel dat de meeste manieren I/O nodig hebben.

Ongeacht de implementatie wil ik het aanroepen van een repository consistent houden; Iedere implementatie moet een identieke interface hebben. Ik zorg er dus voor dat ik mijn repository asynchroon laat zijn "by design" zodat de implementatie ruimte heeft om zijn handelingen asynchroon uit te voeren.

Alle repository methodes zijn dus nu asynchroon, zelfs als dat niet nodig is. Mijn interactions maken gebruik van een repository om eventueel data in op te slaan. Omdat repositories asynchroon werken, moeten mijn interactions ook asynchroon werken voor het geval dat er calls naar een repository nodig zijn. Ook de interface van iedere interaction wil ik consistent houden, dus ook deze maak ik asynchroon by design.

Na een paar daagjes code kloppen had ik een mini applicatie staande. Deze zag er ruwweg zo uit:


code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
entities/
  Project(id: int, name: string, createdAt: datetime, updatedAt: datetime)

interactons/
  CreateProject(name: string)
    => { id: int, name: string, createdAt: datetime, updatedAt: datetime }
  IndexProjects()
    => [{ id: int, name: string, createdAt: datetime, updatedAt: datetime }]

repositories/
  RuntimeRepository
    .putProject()
    .findProjects()
  MysqlRepository
    .putProject()
    .findProjects()



Dat is een mooi begin. Ik heb nu een ultra kleine applicatie waar ik verder mee kan. Met zo'n 150 tests voor achterliggende abstracties om het toevoegen van nieuwe entities, interactions en repositories makkelijker te maken, ben ik klaar om een delivery aan mijn applicatie toe te voegen.

De WebDelivery

Ik wil mijn applicatie eerst op het web hebben. Ik begin dus met het aanmaken van een WebDelivery. Ik stel mezelf de regels dat een delivery nooit toegang krijgt tot een repository of een entity, ook niet als return value. Een delivery mag alleen gebruik maken van de interactions van mijn applicatie.

Voor mijn applicatie ga ik 3 pagina's nodig hebben:
GET /
GET /nieuw-project
POST /nieuw-project

Mijn WebDelivery zal gebruik maken van Express voor routing. Verder zal ik gebruik maken van controllers om de input van een HTTP request om te zetten tot 1 of meerdere calls naar interactions, indien nodig. De controllers koppel ik aan routes in de web delivery.

In tegenstelling tot gebruikelijke controllers in web applicatie frameworks, zullen de controllers waar ik over spreek verantwoordelijk zijn voor een enkele taak. Zo zal ik een HomeController toevoegen voor de home pagina, die voor nu een index van mijn projecten zal genereren, en een CreateProjectController voor het maken van een nieuw project, zowel het renderen van het formulier als het daadwerkelijk aanmaken van het project.

Interactions zijn asynchroon by design omdat deze eventueel async calls naar een repository moet maken. Omdat controllers mogelijk gebruik maken van 1 of meer interactions, zullen de controllers ook weer asynchroon opgezet worden.

De controllers voeren alle interactions uit die nodig zijn en maken o.b.v. hiervan een compleet response object, dat bestaat uit enkel een hash. Deze hash wordt meegegeven aan een template renderer voor het uiteindelijke HTML document in de response.

Alles asynchroon

Deze structuur brengt vele voordelen met zich mee. Ten eerste ben ik volledig onafhankelijk van hoe mijn applicatie geserveerd wordt aan de gebruiker. Dit kan via het web zijn, zoals dit voorbeeld, maar het kan net zo goed via CLI of een desktop app. Hiervoor moet je alleen maar een nieuwe delivery toevoegen.

Als ik nu mijn template renderer dusdanig aanpas dat ik het in fragmenten render, kan ik alvast beginnen met het renderen van een HTML fragment wanneer de achterliggende eventuele interaction klaar is. Het renderen van de HTML maak ik dan asynchroon door het in een worker te doen, net zoals alle andere zwaardere Javascript berekeningen.

Stel dat ik een homepage heb met een index van projecten, een index van todos en een index van ontvangen berichten. Deze pagina zal bestaan uit 3 interactions: IndexProjects, IndexTodos en IndexMessages. Deze 3 interactions kunnen asynchroon tegelijk uitgevoerd worden. Op de pagina zelf zal ik alles tonen in aparte tabellen, dus ik kan direct al een HTML fragment bestaande uit een tabel renderen wanneer de betreffende interaction klaar is, nog voordat de andere interactions klaar zijn met hun rekenwerk.

Zo kan ik een complexe pagina bestaande uit vele onderdelen net zo snel maken als het traagste "blok" in de pagina en worden alle cores van het systeem (of zelfs meerdere systemen) optimaal benut wanneer een zo snel mogelijke response van belang is.

In praktijk gaat dit zeker uitmaken. Als ik kijk naar mijn bestaande websites bestaat vrijwel iedere pagina uit meerdere van dit soort "blokken". Menu's zijn mogelijk dynamisch, een blogpost pagina heeft een blogpost, een blokje met vorige berichten, een blokje met reacties en vast nog wel veel meer.

Ik heb websites gemaakt met pagina's waarin 20 tot 30 van dit soort "blokken" voorkomen. Performance is altijd een issue op zo'n pagina. Vaak is het een homepage of dashboard. Uit ervaring weet ik ook dat de grootste performance issue bij mijn applicaties vaak ligt in het verwerken van de data tot HTML en niet in het ophalen ervan. Er is echter altijd wel rekenkracht beschikbaar op een server om dit asynchroon uit te voeren over meerdere cores of zelfs meerdere servers, wat nu mogelijk is met de asynchrone en modulaire opzet van mijn applicatie.

Conclusie

De conclusies zover is dat alles asynchroon maken van mijn applicatie en alles modulair maken ervoor heeft gezorgd dat mijn applicatie flexibel is bij veranderingen. Verder kan ik al mijn tests draaien in een fractie van een seconde omdat ik zeer zelden meer test dan nodig: Ik hoef geen database te gebruiken bij mijn tests of HTTP calls te doen voor het testen van core features.

Ik weet nog niet wat ik verder met al mijn ontwikkelingen ga doen. Ik ga zeer spoedig fulltime als freelancer aan de slag, dus ik heb de mogelijkheid zelf te bepalen hoe ik mijn werk doe. Ik wil er wat mee doen, maar voor nu kost het allemaal veel tijd om te experimenteren en ontwikkelen voordat ik wat nuttigs kan opzetten.

Voorlopig blijf ik dus werken met Ruby on Rails. Daarnaast zal ik dit project verder ontwikkelen om hopelijk ooit te kunnen showen. Ik heb veel zelf geschreven en vrijwel alles wat ik heb geschreven is modulair, gedekt met tests en herbruikbaar. Misschien kan ik er in de toekomst wat "echte" projecten op ontwikkelen.