Upgrade to Pro — share decks privately, control downloads, hide ads and more …

DSLs JavaScript

DSLs JavaScript

Entendo DSLs através do exemplo prático do Azkfile.js, o arquivo manifesto do projeto http://azk.io

Everton Ribeiro

September 24, 2015
Tweet

More Decks by Everton Ribeiro

Other Decks in Programming

Transcript

  1. <!DOCTYPE HTML> <html lang="en-US" > <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible"

    content="IE=11; IE=10; IE=9; IE=8; IE=7; IE=EDGE" /> <title>azk Docs Gitbook</title> </head> <body> <!-- ... --> </body> </html> HTML
  2. Manipulate[ Graphics[ Table[{ If[EvenQ[i], Black, White], Disk[{0, If[EvenQ[i], 0, Rescale[i,

    {0, n}, (d/n) {1, -1}]]}, Rescale[i, {0, n}, {1, r}]] }, {i, 0, n}]], {{n, 31, "resolution"}, 1, 51}, {{d, 1, "displacement"}, -10, 10}, {{r, 0, "radius"}, 0, 1} ] WOLFRAM LANGUAGE
  3. ActiveRecord::Schema.define(version: 20141102103617) do create_table "users", force: true do |t| t.string

    "password_digest" t.datetime "created_at" t.datetime "updated_at" t.boolean "setup_complete" t.string "api_key" end end RAILS (MIGRATIONS)
  4. describe("azk config module", function() { // Don’t change ‘env’ in

    test var env = config('env'); afterEach(() => set('env', env)); it("should get a env key", function() { h.expect(config('env')).to.equal('test'); h.expect(get('env')).to.equal('test'); }); }); EXPRESS.JS MOCHA (BDD STYLE)
  5. var express = require('express'); var app = express(); app.get('/', function

    (req, res) { res.send('Hello World!'); }); EXPRESS.JS EXPRESS.JS
  6. var express = require('express'); var app = express(); app.get('/', function

    (req, res) { res.send('Hello World!'); }); app.delete('/user', function (req, res) { res.send('Got a DELETE request at /user'); }); EXPRESS.JS EXPRESS.JS
  7. NATIVA OU SUBSET HTML tem interpretadores nativos, Rails Migrations e

    Express.js são subsets de uma linguagem de propósito geral
  8. INTERNA VS EXTERNA Ainda que limitada, uma DSL interna é

    mais fácil de se criar pois não precisa de um parser ou gramáticas próprias
  9. // Express.js DSL var express = require('express'); var app =

    express(); app.get('/', function (req, res) { res.send('Hello World!'); }); // No DSL var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World!'); }).listen(9615); EXPRESS.JS DSL vs NO-DSL
  10. PLUG-IN OU API Não precisa aprender todos os detalhes internos

    para usar uma API ou estender uma aplicação
  11. azk

  12. ORQUESTRADOR DE AMBIENTES DE DESENVOLVIMENTO Ferramenta simples e open source

    que vai lhe ajudar a manter seu ambiente de desenvolvimento azk
  13. FAZ ISSO ATRAVÉS DE UM ARQUIVO MANIFESTO SIMPLES: Azkfile.js O

    Azkfile.js provê uma descrição sucinta dos componentes que formam a arquitetura da aplicação Azkfile JS
  14. systems({ api: { image: { docker: "azukiapp/ruby" }, }, mysql:

    { image: { docker: "azukiapp/mysql" }, }, });
  15. systems({ api: { image : { docker: "azukiapp/ruby" }, depends:

    [ "mysql" ], //... }, mysql: //..., });
  16. systems({ api: { image : { docker: "azukiapp/ruby" }, depends:

    [ "mysql" ], ports : { http: "80/tcp" }, }, mysql: { image: { docker: "azukiapp/mysql" }, ports: { data: "3306/tcp" }, }, });
  17. systems({ api: { image : { docker: "azukiapp/ruby" }, depends:

    [ "mysql" ], ports: { http: "80/tcp" }, http : { domains: [ "#{system.name}.dev.azk.io" ]}, }, mysql: { image: { docker: "azukiapp/mysql" }, ports: { data: "3306/tcp" }, }, });
  18. systems({ api: { image : { docker: "azukiapp/ruby" }, depends:

    [ "mysql" ], ports: { http: "80/tcp" }, http : { domains: [ "#{system.name}.dev.azk.io" ]}, }, mysql: { image: { docker: "azukiapp/mysql" }, ports: { data: "3306/tcp" }, }, });
  19. { // Comments are not allowed "systems""": { "api": {

    "images": { "docker": "azukiapp/node" }, "command": "npm start", "http": { "domains": [ "api.dev.azk.io" ] } }, "mysql": { "images": { "docker": "azukiapp/mysql" } } }, defaultSystem: "api", } EXPRESS.JS Azkfile.json
  20. { // Comments are not allowed "systems""": { "api": {

    "images": { "docker": "azukiapp/node" }, "command": "npm start", "http": { "domains": [ "api.dev.azk.io" ] } }, "mysql": { "images": { "docker": "azukiapp/mysql" } } } defaultSystem: "api", } EXPRESS.JS Azkfile.json
  21. systems: api: image: docker: "azukiapp/node" command: "npm start" http: domains:

    - azk.dev.azk.io mysql: &mysql image: docker: "azukiapp/mysql" mysql-cron: << *mysql ports: 3306: null default: systems EXPRESS.JS Azkfile.yml
  22. systems: api: image: docker: "azukiapp/node" command: "npm start" http: domains:

    - azk.dev.azk.io mysql: &mysql image: docker: "azukiapp/mysql" mysql-cron: << *mysql ports: 3306: null default: systems EXPRESS.JS Azkfile.yml
  23. Existe implementação para todas plataformas e SOs, além dos binds

    para as mais diversas linguagens JavaScript <3 <3 <3 JS
  24. function domain(prefix) { var sufix = (env.NODE_ENV === "production") ?

    ".azk.io" : ".dev.azk.io"; return prefix + sufix; } system("api", { image: { docker: "azukiapp/ruby" }, http: { domains: [ domain("#{system.name}") ] }, });
  25. - Simples para o usuário entender; - Extensível; - Escopo

    fechado; - Não-verboso; - Fácil de manipular (parser e geração) Azkfile.js Azkfile JS
  26. describe("azk config module", function() { // Don’t change ‘env’ in

    test var env = config('env'); afterEach(() => set('env', env)); it("should get a env key", function() { h.expect(config('env')).to.equal('test'); h.expect(get('env')).to.equal('test'); }); }); EXPRESS.JS MOCHA (BDD STYLE)
  27. DSLs FOR DESCRIPTION STATE MACHINES Bonitas, porém pouco flexíveis. E

    nada simples de implementar: exigem muita manipulação do contexto http://to.azk.io/composing-dsls-in-javascript
  28. {do: [ {ask: "Enter file name: ", type: "file"}, {fetchFile:

    {showProgress: "progress_bar"}, reportInterval: 1.0}, {spawn: {withRetry: {uploadToDropbox: {user: "cat", password: "meow"}}, maxTries: 5, onfail: {do: [ {deleteTempFiles: null}, {ask: "Dropbox upload failed. Try again?", type: "yes/no", yes: {retry: true}, no: {raise: "Give up"}} ]}}}, {cacheInLocalStore: "prefix"}, {showInElement: "element_id"} ]} EXPRESS.JS J EXPRESSIONS
  29. J EXPRESSIONS: {opname: input_object} Simples de implementar mas oferece poucos

    ganhos em relação ao JSON padrão http://to.azk.io/dsls-j-expressions {.js}
  30. var system = require('azkfile').system; var disable = require('azkfile').disable; var setDefault

    = require('azkfile').setDefault; system("mysql-cron", { extends: "mysql", ports: { 3306: disable }, }); setDefault("api");
  31. module.exports = function(conf) { conf.system("mysql-cron", { extends: "mysql", ports: {

    3306: conf.disable }, }); conf.setDefault("api"); }; // require('./Azkfile.js')(dsl);
  32. NODE MODULE Não deixa claro o require, mas além dele

    estar lá, ainda exige entender o que é um "module.exports"
  33. require('vm').runInNewContext Compila o código, contextifica uma sandbox existente (se informada)

    ou cria uma nova sandbox para aquele contexto http://to.azk.io/runInNewContext {.js}
  34. var util = require('util'); var vm = require('vm'), var sandbox

    = { animal: 'cat', count: 2 }; vm.runInNewContext('count += 1; name = "kitty"', sandbox); console.log(util.inspect(sandbox)); // { animal: 'cat', count: 3, name: 'kitty' }
  35. var util = require('util'); var vm = require(‘vm’), fs =

    require('fs'); var azkfile_sandbox = { system : function() { /* ... */ }, disable: null, /* ... */ }; var azkfile = fs.readFile('./Azkfile.js'); vm.runInNewContext(azkfile, azkfile_sandbox);
  36. SIMPLES E FLEXÍVEL Um contrato claro é estabelecido, apenas aquilo

    que queremos está disponível no Azkfile.js {.js}
  37. GLOBALS NÃO!!! Nada do que é definido no Azkfile.js vaza

    para o contexto global e compromete o funcionamento do azk {.js}
  38. MAS AINDA ESTÃO FALTANDO ALGUMAS COISAS… No mundo ideal, o

    usuário nunca escreveria um Azkfile.js inválido. No mundo ideal ... {.js}
  39. var file = "./Azkfile.js" var content = fs.readFile(file); try {

    vm.runInNewContext(content, azkfile_sandbox, file); } catch (e) { var stack = e.stack.split('\n'); var msg = stack[0] + "\n" + stack[1]; throw new ManifestError(file, msg); }
  40. var check = require('syntax-error'); var file = "./Azkfile.js" var content

    = fs.readFile(file); var err = check(content, file); if (err) { throw new ManifestError(file, err); } else { try { vm.runInNewContext(content, azkfile_sandbox, file); } catch (e) { var stack = e.stack.split('\n'); var msg = stack[0] + "\n" + stack[1]; throw new ManifestError(file, msg); } }
  41. var DSLHelper = require('dsl-helper').DSLHelper; // Creating the DSL by primitives

    var dsl = new DSLHelper({ console: console, log: function() { console.log(this); } }); // call with scope and code dsl.execute({ name: "David" }, "log();"); // { name: "David" }
  42. dsl-helper: SUPORTE A NODE.JS E IO.JS (AKA NODE.JS 4.0) O

    io.js introduziu tratamento adequado de erros de sintaxe no runInNewContext
  43. NPM INIT Baseado em um wizard, mas gera apenas um

    “dump" de uma estrutura JSON >_
  44. { "name": "example", "version": "1.0.0", "description": "npm init example", "main":

    "index.js", "dependencies": {}, "devDependencies": {}, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Everton Ribeiro <[email protected]>", "license": "Apache-2" }
  45. AZK INIT (ATUALMENTE) Usa um template (o que permite ir

    além de um dump), mas não permite customização depois de gerado >_
  46. // Adds the systems that shape your system systems({ {{~#each

    systems}} {{&hash_key @key}}: { // Dependent systems depends: {{&json depends}}, // ... },{{/each}} }); {{#if defaultSystem}} // Sets a default system setDefault("{{&defaultSystem}}"); {{~/if}}
  47. TEMPLATES SÃO DIFÍCEIS DE SE MANTER Diferente de um template

    HTML, espaçamentos e quebras de linha são importantes
  48. NÃO É POSSÍVEL ADICIONAR CÓDIGO Uma vez que o usuário

    modificou o arquivo, não é possível adicionar mais código dinamicamente
  49. var recast = require("recast"); // Original code var code =

    [ "function add(a, b) {", " return a +", " // Weird formatting, huh?", " b;", "}" ].join("\n"); var ast = recast.parse(code); // Parse the code var output = recast.print(ast).code; // Generate code by ast console.log(code === output); // true
  50. var recast = require("recast"); // Original code var code =

    "var a = 'foo'"; var ast = recast.parse(code); // Rename var `a` to `bar` ast.program.body[0].declarations[0].id.name = "bar"; console.log(recast.print(ast).code); // var bar = 'foo'
  51. MANIPULAÇÃO DA AST É O CAMINHO E A VIDA Com

    recast, eu posso não apenas manipular o código do Azkfile.js, como posso também manter todas as customizações do usuário
  52. AZK INIT (FUTURO) Usa manipulação de AST para gerar o

    Azkfile.js, evitando problemas com formatação do template >_
  53. AZK ADD (FUTURO) Comando que vai permitir adicionar novos systems

    ao Azkfile.js sem mudar a formatação ou remover comentários >_
  54. DSLs NÃO SÃO BALAS DE PRATA DSLs são mais fáceis

    de se aprender, mas são novas linguagens a serem aprendidas
  55. MEDO TER VOCÊ NÃO DEVE: “MEDO É O CAMINHO PARA…"

    Não precisa começar por algo elaborado. Experimente e observe os exemplos: existem vários muito bons no mundo JS
  56. DE ALGUMA FORMA, VOCÊ JÁ ESTÁ USANDO UMA azk, gulp,

    grunt, mocha, express.js, sass…
  57. UTILIZE E NOS DÊ SEU FEEDBACK Issues e Pull Request

    são sempre bem vindos no Github