Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
DSLs JavaScript
Search
Everton Ribeiro
September 24, 2015
Programming
5
290
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
Share
More Decks by Everton Ribeiro
See All by Everton Ribeiro
Arquiteturas Executáveis - WeOp Summit
nuxlli
3
99
Arquiteturas Executáveis
nuxlli
5
110
Other Decks in Programming
See All in Programming
3rd party scriptでもReactを使いたい! Preact + Reactのハイブリッド開発
righttouch
PRO
1
610
PHP でアセンブリ言語のように書く技術
memory1994
PRO
1
170
Amazon Qを使ってIaCを触ろう!
maruto
0
410
最新TCAキャッチアップ
0si43
0
190
Creating a Free Video Ad Network on the Edge
mizoguchicoji
0
120
ActiveSupport::Notifications supporting instrumentation of Rails apps with OpenTelemetry
ymtdzzz
1
250
Macとオーディオ再生 2024/11/02
yusukeito
0
370
광고 소재 심사 과정에 AI를 도입하여 광고 서비스 생산성 향상시키기
kakao
PRO
0
170
ヤプリ新卒SREの オンボーディング
masaki12
0
130
Remix on Hono on Cloudflare Workers
yusukebe
1
300
Contemporary Test Cases
maaretp
0
140
CSC509 Lecture 11
javiergs
PRO
0
180
Featured
See All Featured
Facilitating Awesome Meetings
lara
50
6.1k
Navigating Team Friction
lara
183
14k
10 Git Anti Patterns You Should be Aware of
lemiorhan
655
59k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
226
22k
YesSQL, Process and Tooling at Scale
rocio
169
14k
Writing Fast Ruby
sferik
627
61k
Mobile First: as difficult as doing things right
swwweet
222
8.9k
Being A Developer After 40
akosma
87
590k
RailsConf 2023
tenderlove
29
900
Music & Morning Musume
bryan
46
6.2k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
8
900
StorybookのUI Testing Handbookを読んだ
zakiyama
27
5.3k
Transcript
{ DSLs JavaScript } por @nuxlli
None
azuki
DSL Domain Specific Language Linguagem de domínio específico
LINGUAGEM DE DOMÍNIO ESPECÍFICO É o inverso de uma linguagem
de propósito geral
<!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
< /> HTML Domínio: marcação de conteúdo
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
1/2 WOLFRAM LANGUAGE Domínio: matemática e fórmulas
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)
RAILS (MIGRATIONS) Domínio: database scheme
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)
MOCHA (BDD STYLE) Domínio: specs mocha
var express = require('express'); var app = express(); app.get('/', function
(req, res) { res.send('Hello World!'); }); EXPRESS.JS EXPRESS.JS
EXPRESS.JS Ainda que definida usando uma linguagem de propósito geral,
é uma DSL
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
NATIVA OU SUBSET HTML tem interpretadores nativos, Rails Migrations e
Express.js são subsets de uma linguagem de propósito geral
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
// 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
PLUG-IN OU API Não precisa aprender todos os detalhes internos
para usar uma API ou estender uma aplicação
azk
ORQUESTRADOR DE AMBIENTES DE DESENVOLVIMENTO Ferramenta simples e open source
que vai lhe ajudar a manter seu ambiente de desenvolvimento azk
None
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
systems({ api: { //... }, mysql: { //... }, });
systems({ api: { image: { docker: "azukiapp/ruby" }, }, mysql:
{ image: { docker: "azukiapp/mysql" }, }, });
systems({ api: { image : { docker: "azukiapp/ruby" }, depends:
[ "mysql" ], //... }, mysql: //..., });
systems({ api: { image : { docker: "azukiapp/ruby" }, depends:
[ "mysql" ], ports : { http: "80/tcp" }, }, mysql: { image: { docker: "azukiapp/mysql" }, ports: { data: "3306/tcp" }, }, });
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" }, }, });
system("mysql-cron", { extends: "mysql", ports: { 3306: disable }, });
setDefault("api");
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" }, }, });
Azkfile.js É UMA DSL SIMPLES E INTUITIVA EM JAVASCRIPT Fácil
de entender e personalizar {.js}
DSLs de configuração não eram flexíveis o suficiente FÁCIL DE
ENTENDER E PERSONALIZAR Azkfile JS
{ // 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
{ // 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
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
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
.INI, .TOML ETC. Limitados, confusos, verbosos e principalmente: falta um
padrão
DEVE SER SIMPLES DE LER E ENTENDER Menos atrito e
traduções
JavaScript <3 Todo desenvolvedor conhece pelo menos o básico. JS
É padronizado!! JavaScript <3 <3 JS
Existe implementação para todas plataformas e SOs, além dos binds
para as mais diversas linguagens JavaScript <3 <3 <3 JS
Mais flexível e poderoso do que linguagens de configuração padrão
JavaScript <3 <3 <3 <3 JS
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}") ] }, });
- 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
{ IMPLEMENTANDO DSLs EM JavaScript }
fs.readFileAsync("file.json").then(JSON.parse).then(function(val) { console.log(val.success); }) .catch(SyntaxError, function(e) { console.error("invalid json in
file"); }) .catch(function(e) { console.error("unable to read file"); }); EXPRESS.JS BlueBird
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)
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
{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
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}
LINGUAGEM EXTENDS Simples e elegante, mas pode ser perigosa {.js}
system("mysql-cron", { extends: "mysql", ports: { 3306: disable }, });
setDefault("api");
PRIMITIVAS: systems, env, setDefault, disable… Precisam ser carregas ou colocadas
no escopo global {.js}
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");
REQUIRE? VISH!!! Permite ao usuário carregar qualquer coisa, inclusive coisas
que ele não devia! {.js}
module.exports = function(conf) { conf.system("mysql-cron", { extends: "mysql", ports: {
3306: conf.disable }, }); conf.setDefault("api"); }; // require('./Azkfile.js')(dsl);
NODE MODULE Não deixa claro o require, mas além dele
estar lá, ainda exige entender o que é um "module.exports"
{ require(‘vm') }
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}
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' }
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);
SIMPLES E FLEXÍVEL Um contrato claro é estabelecido, apenas aquilo
que queremos está disponível no Azkfile.js {.js}
GLOBALS NÃO!!! Nada do que é definido no Azkfile.js vaza
para o contexto global e compromete o funcionamento do azk {.js}
MAS AINDA ESTÃO FALTANDO ALGUMAS COISAS… No mundo ideal, o
usuário nunca escreveria um Azkfile.js inválido. No mundo ideal ... {.js}
sistema("mysql-cron", { ports: { 3306: disable }, });
None
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); }
None
system("mysql-cron", { ports: { 3306: disable }, }
None
https://npmjs.com/package/syntax-error
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); } }
None
{ require(‘dsl-helper’) }
https://github.com/azukiapp/dsl-helper
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" }
PRIMITIVAS + ESCOPO + CÓDIGO = DSL
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
{ Geradores de DSL }
NPM INIT Baseado em um wizard, mas gera apenas um
“dump" de uma estrutura JSON >_
{ "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" }
AZK INIT (ATUALMENTE) Usa um template (o que permite ir
além de um dump), mas não permite customização depois de gerado >_
// 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}}
TEMPLATES SÃO DIFÍCEIS DE SE MANTER Diferente de um template
HTML, espaçamentos e quebras de linha são importantes
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
{ AST (AVANÇADO) }
https://www.npmjs.com/package/recast
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
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'
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
AZK INIT (FUTURO) Usa manipulação de AST para gerar o
Azkfile.js, evitando problemas com formatação do template >_
AZK ADD (FUTURO) Comando que vai permitir adicionar novos systems
ao Azkfile.js sem mudar a formatação ou remover comentários >_
{ CONCLUSÃO }
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
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
DE ALGUMA FORMA, VOCÊ JÁ ESTÁ USANDO UMA azk, gulp,
grunt, mocha, express.js, sass…
{ MAIS INFORMAÇÕES }
DOCUMENTAÇÃO http://docs.azk.io NOSSO GITHUB https://github.com/azukiapp/
BLOG http://medium.com/azuki-news CHAT http://gitter.im/azukiapp/azk/
{ CONTRIBUINDO COM A AZUKI }
UTILIZE E NOS DÊ SEU FEEDBACK Issues e Pull Request
são sempre bem vindos no Github
ESTRELAS NO https://github.com/azukiapp/azk Faça agora, é simples e pode ajudar
mais do que você imagina ;)
OBRIGADO Éverton Ribeiro // @nuxlli
[email protected]
Sponsors