Peter Širka - Brutal Web Developer
Jednoduchý pattern pre tvorbu Total.js aplikácií

Sun Sep 11 2016 11:25:20 GMT+0200 (Central European Summer Time)

Jednoduchý pattern pre tvorbu Total.js aplikácií

Total.js framework vyvíjam už niekoľko rokov a veľmi dlhý čas z tohto vývoja som hľadal vhodný spôsob, ako tvoriť aplikácie. Vytvoril som veľmi veľa aplikácií a stále to nebolo ono, preto som sa trošku inšpiroval Node.js modulom Mongoose.js a jeho schémami.

Total.js schémy

Považujem ich za základnú stavebnú jednotku všetkých Total.js aplikácií, pretože do schém ukladám celú business logiku, validácie a všetko okolo toho. Pomocou schém sa mi podarilo urobiť poriadok a prehľadnosť v zdrojových kódoch všetkých aplikácií. Takže na otázku čo sú vlastne Total.js schémy? Odpoviem jednoducho, že schéma je len nejaký objekt s preddefinovanou štruktúrou vlastností a metód, nič viac.

Hlavné výhody

  • dodáva prehľadnosť do kódu
  • podporuje veľa dátových typov (String, Boolean, Number, Date, Email, Phone, atď.)
  • podporuje validácie
  • podporuje klasické CRUD operácie save, get, delete a query
  • podporuje workflows
  • podporuje transformations
  • podporuje hooks
  • podporuje operations
  • podporuje lokalizácie
  • podporuje predvolené hodnoty
  • podporuje asynchrónne operácie (veľmi dôležitá funkcia)
  • vývojár sa môže vždy spoľahnúť na zadefinovaný dátový typ
  • ako dátový typ môže byť použitá iná schéma
  • má vstavený error handling (veľmi dôležitá funkcia)
  • na schému je možné napojiť routing
  • vlastnosti, ktoré nie sú definované v schéme, sú automaticky vymazané
  • a mnoho ďalšieho

Ukážka schémy

Ak začnete používať Total.js tak narazíte na množstvo divných názvov, ale postupne si určite zvyknete. Funkcia NEWSCHEMA() vytvorí novú schému a .make() vytvorí len privátny scope na deklaráciu schémy.

models/newsletter.js:

NEWSCHEMA('Newsletter').make(function(schema) {

    // Alebo:
    // var schema = NEWSCHEMA('Newsletter');

    // schema.define('property name', 'Data Type', [required], [filter]);
    schema.define('email', 'Email', true);

    // Overí email v databáze
    schema.addWorkflow('check', function(error, model, options, callback) {

        // SQL Agent automaticky podporuje error handling zo schém
        var sql = DB(error);

        sql.exists('newsletter', 'tbl_newsletter').make(function(builder) {
            builder.where('email', model.email);
            builder.where('isremoved', false);
        });

        // Nižšie uvedená funkcia skontroluje, či newsletter obsahuje nejaký záznam.
        // V prípade, že obsahuje chybu tak podľa aktuálnej lokalizácie sa vyberie podľa
        // kľúča "error-email-exists" správa z resources.
        sql.validate('newsletter', 'error-email-exists', true);

        // Exec vyvolá všetky vyššie uvedené DB operácie v poradí, v akom sú zadané.
        sql.exec(n => callback(SUCCESS(true)));            
    });

    // Uloží model do DB
    schema.setSave(function(error, model, controller, callback) {
        var sql = DB(error);

        sql.insert('tbl_newsletter').make(function(builder) {
            builder.set('email', model.email);
            builder.set('created', F.datetime);
            builder.set('ip', controller.ip);        
            builder.set('isremoved', false);    
        });

        sql.exec(n => callback(SUCCESS(true)));
    });

    // Notifikuje užívateľa o tom, že sa zaregistroval
    schema.addWorkflow('email', function(error, model, options, callback) {
        // Nastavenie SMTP sa nachádza v configu
        var mail = F.logmail(model.email, 'Boli ste pridaný na odber noviniek', 'Ďakujeme, že ste sa rozhodli odoberať novinky z nášho internetového servera.');
        mail.reply('petersirka@gmail.com', true);
        callback(SUCCESS(true));
    });
});

Takže vytvoril som schému Newsletter a túto schému teraz môžeme prepojiť s formulárom pre odoberanie noviniek emailom. V prípade, že do schémy pridú iné data, tak framework ich jednoducho odstráni, pretože sa nenachádzajú v schéme.

controllers/api.js:

exports.install = function() {
    // Zadafinuje sa trasa a pomocou flags priradíme odkaz na schému "Newsletter"
    // Trasa načúva na POST metódu
    F.route('/api/newsletter/', json_newsletter_save, ['*Newsletter', 'post']);
}

// Akcia na uloženie
function json_newsletter_save() {
    var self = this;

    // self === controller

    // self.body obsahuje validné data podľa schémy a táto property je obalená
    // internými funkciami schémy.

    // self.body.$async() metóda je zo schém a urobí to, že začne za sebou volať
    // všetky vymenované operácie v poradí, v akom sú zadané.

    // Ak nejaká operácia skončí chybou, tak framework ukončí ďalšie volania
    // a nebude pokračovať ďalej + vyvolá ihneď callback() s chybami.

    // self.body.$async(callback, [return_index])
    // v prípade, že nie je uvedený return_index, tak ako výsledok v callbacku
    // bude pole výsledkov z každej operácie v poradí, v akom boli volané. Čiže
    // nižšie uvedený kód vráti výsledok z metódy $save(), pretože return_index
    // je "1", keby bol "0" tak vráti výsledok z workflowu "check".

    self.body.$async(self.callback(), 1).$workflow('check').$save(self).$workflow('email');
}

Ďalšie špecialitky

Error Handling ErrorBuilder:
Považujem ho za veľmi dôležitú časť frameworku a podporuje skutočne zaujímavé funkcie. Môžete do neho vkladať rôzne chyby (aj viacej naraz), ďalej je prepojený s resources (podpora lokalizácie) a výstup môžete transformovať na čo len chcete (stačí zaregistrovať transformáciu a dokonca nami vytvorenú transformáciu môžete nastaviť ako predvolenú default). Chyby sa ukladajú do Array, pričom každá chyba musí mať svoj identifikátor. Zároveň pri automatickom validovaný v schémach ErrorBuilder obsahuje aj cestu k property, poprípade Array index, takže krásne viete zistiť pôvod chyby aj v rôznych vnorených schémách alebo v poli.

error.push('fieldname1', 'error message or error object');
error.push('fieldname2', 'error message or error object');
error.push('fieldname3', 'error message or error object');
error.push('fieldname4', 'error message or error object');
// Napr. rozšírené použitie ErrorBuildera v schéme:
if (model.age < 18)
   error.push('age', 'Nemáte ešte 18 rokov');

if (!model.terms)
   error.push('terms', 'Nesúhlasili ste s podmienkami internetového servera.');

if (error.hasError())
    return callback();

V predvolenom režime je výstup ErrorBuilder veľmi jednoduchý a framework vždy vracia pole:

[{
    "name": "email",
    "error": "Your <b>e-mail address</b> @ is not valid.",
    "path": "Support.email"
}, {
    "name": "firstname",
    "error": "The field <b>First name</b> is required.",
    "path": "Support.firstname"
}, {
    "name": "lastname",
    "error": "The field <b>Last name</b> is required.",
    "path": "Support.lastname"
}, {
    "name": "body",
    "error": "The field <b>Issue</b> is required.",
    "path": "Support.body"
}]

Na client-side sa dá veľmi jednoducho overiť či data obsahujú chybu:

// jComponent AJAX call:
AJAX('GET /api/support/', function(response) {

   if (response instanceof Array) {
      console.log('Server vrátil chybu!');
      return;
   }

});

Následne podľa vlastností v poli name a path viete krásne označiť vo formulári, ktorých prvkov sa chyba týka. Moja filozofia prepojenia JSONovo server-side a client-side je v 2 podmienkach:

  • chyby zo servera sú vždy vracané ako Array (viď vyššie uvedený kód)
  • opak pola, čiže Object, String, Number, Boolean je považovaný za požadovanú response

Rozšírenie schémy:
Rozšíriť schému (narážam na modulárnosť) je veľmi jednoduché a možete to urobiť na hociktorom mieste v aplikácii:

GETSCHEMA('Newsletter').make(function(schema) {
    schema.addWorkflow('whatever', function(error, model, options, callback) {
        callback();
    });

    schema.addHook('whatever', function(error, model, options, callback) {
        callback();
    });
});

Auto trim stringov:
Toto bol jeden skvelý nápad pridať do schém. V prípade, že Vám hodnody v string vlastnostiach obsahujú na začiatku a na konci biele znaky \n, \r, \s, \t - tak ich framework automaticky odstráni. Môžem Vám z úprimného srdca povedať, že od vtedy ako je toto implementované, mám čisté databázy (hodnotovo). Hodnoty v schéme sú automaticky trimované (by default) a toto správanie sa dá vypnúť cez property schema.trim = false.

Vnorené schémy:
Ďalšou skvelou funkciou sú vnorené schémy, pomocou ktorých môžete mať prepojené rôzne schémy/modely medzi sebou. Ako príklad uvediem objednávku a jej adresy:

NEWSCHEMA('Address').make(function(schema) {
    schema.define('street', 'String(50)', true);
    schema.define('city', 'Capitalize(30)', true);
    schema.define('zip', 'Zip', true);
    // ...
});

NEWSCHEMA('Order').make(function(schema) {
    schema.define('billingaddress', 'Address');
    schema.define('postaladdress', 'Address');
    // ...
});

Polia - Array:
Aj Array môžete zaevidovať ako dátový typ:

    // Pole string.toLowerCase() s maximálnou dĺžkou 25 znakov
    schema.define('tags', '[Lower(25)]');
    schema.define('emails', '[Email]');

    // Pole čísiel a je povinné, takže pole musí mať viac prvkov ako 0
    schema.define('numbers', '[Number]', true);

    // Pole ďalších Total.js schém
    schema.define('addresses', '[Address]');

Asynchrónne operácie:
Podporujú toho oveľa viacej ako som popísal. Dynamicky viete upraviť volania, návratové hodnoty a dokonca každá inštancia schémy obsahuje skrytý objekt na zapisovanie ďalších potrebných (temporary) údajov. Tieto údaje sú jednotné skrz všetky asynchrónne operácie.

model.$repository('my-custom-value', 34030);
console.log(model.$repository('my-custom-value));

Validácie:
Total.js sa snaží ušetriť vývojárom kopec roboty a preto aj väčšina validácií je implementovaná v jadre. V prípade, že chcete vytvoriť validáciu na nejakú property v schéme tak budete postupovať takto:

schema.setValidate(function(name, value, path, schema, model) {
    switch (name) {
        case 'cardnumber':
            return value.length === 17;
        case 'cardtype':
            return value.match(/VISA|MASTERCARD|MAESTRO/) ? true : false;
    }
});

Predvolené hodnoty:
Schémy podporujú aj metódu na vygenerovanie predvolených hodnôt. Takže v prípade, že sa generuje nová schéma tak je volaný nižšie uvedený delegát.

schema.setDefault(function(name) {
    switch (name) {
        case 'terms':
        case 'newsletter':
           return true;
        case 'email':
           return '@';
    }
});

Meníme hodnoty ešte pred validáciou:
V prípade, že pred validáciou potrebujete upraviť ešte nejaké prijaté hodnoty v schéme, tak je možné využiť metódu podľa nižšie uvedeného kódu:

schema.setPrepare(function(name, value) {
    switch (name) {
        case 'cardtype':
           // e.g. visa => VISA
           return value.toUpperCase();
    }
});

Schéma bez vlastností/fieldov? No problem
Sú prípady, kedy nepotrebujete definovať žiadne dátové typy / vlastnosti, proste potrebujete nejaký objekt, ktorý má nejaké metódy a v tomto prípade je k dispozícii workflows, transformations, operations, atď., príklad:

NEWSCHEMA('Empty').make(function(schema) {
    schema.addWorkflow('send', function(error, model, options, callback) {
        // model bude vždy prázdny objekt typu SchemaInstance {}
        callback();
    });
});

Vytvorenie objektu podľa schémy:
Nižšie uvedený kód vytvorý objekt podľa schémy s predvolenými hodnotami a zabalenými internými funkciami. Tento kód je možné volať v celom frameworku - v *.js súboroch.

var Newsletter = GETSCHEMA('Newsletter');
var obj = Newsletter.create();

obj.email = 'petersirka@gmail.com';
obj.$save();

Ako skúsiť schémy?
Nemusíte vytvárať projekt na to, aby ste skúsili schémy. Stačí vytvoriť jednoduchý .js script a odkázať sa na require('total.js').

require('total.js');

var Newsletter = NEWSCHEMA('Newsletter');

Newsletter.make(function(schema) {
   // definícia
   schema.define('email', 'Email');

   schema.setSave(function(error, model, options, callback) {
       console.log('model (ako SchemaInstance)', model);
       console.log('čistý objekt napr. na serializáciu', model.$clean());
       console.log('options', options);
       console.log('saved');
       callback(SUCCESS(true));
   });
});

var obj = Newsletter.create();

obj.email = 'petersirka@gmail.com';
obj.$save({ custom: 'options' }, function(err, response) {
    console.log('---> RESPONSE', err, response);
});

Je toho ešte veľmi veľa čo obsahujú schémy, ale pre rýchly začiatok a predstavu to bude hádam postačovať. Pre vytvorenie schém je možné použiť aplikáciu AppDesigner.

Schémy v projektoch

Nižšie uvedené projekty sú postavené na schémach, takže ak chcete vidieť reálne projekty - tak doporučujem naštudovať tieto zdrojové kódy na GitHube:


Značky


Posledné blogy
Brutálny polrok 2018
Fri Jun 22 2018 09:48:21 GMT+0200 (Central European Summer Time)
Konferencia/Workshop: TotalCon 2017
Thu Nov 16 2017 22:06:55 GMT+0100 (Central European Standard Time)
CodeCon 2017 z pohľadu organizátora
Sun Apr 09 2017 09:27:34 GMT+0200 (Central European Summer Time)
Elektrický bicykel KTM MACINA KAPOHO SLX 2017
Fri Mar 31 2017 15:32:37 GMT+0200 (Central European Summer Time)
Total.js platforma - školenia
Mon Jan 16 2017 18:35:05 GMT+0100 (Central European Standard Time)

Posledné komentáre
Thank you for your work! You are the great friend, great person and great expert!
Jozef Gula
Mon Jun 25 2018 08:02:14 GMT+0200 (Central European Summer Time)
It's been an absolute pleasure collaborating with you Peter, I've learned so much from Total.js a...
Pedro Costa
Fri Jun 22 2018 19:18:22 GMT+0200 (Central European Summer Time)
Tento skateboard až tak kopec nerieši, pretože má 2 silné motory. So mnou vyšiel všade, kde som b...
Peter Širka
Fri Jul 14 2017 09:30:56 GMT+0200 (Central European Summer Time)
Dobry den jak se vypořadavá s kopci ?
Lukáš
Thu Jul 13 2017 20:44:43 GMT+0200 (Central European Summer Time)
Chlapci z firmy Nazaret Pánu Bohu ďakujú a Tebe blahoželajú k dobre odvedenej práci. Super Peťo, ...
Tomáš
Thu Dec 15 2016 08:08:15 GMT+0100 (Central European Standard Time)