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
300
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
110
Arquiteturas Executáveis
nuxlli
5
120
Other Decks in Programming
See All in Programming
抽象化という思考のツール - 理解と活用 - / Abstraction-as-a-Tool-for-Thinking
shin1x1
1
950
コーディングは技術者(エンジニア)の嗜みでして / Learning the System Development Mindset from Rock Lady
mackey0225
2
320
あのころの iPod を どうにか再生させたい
orumin
2
2.4k
『リコリス・リコイル』に学ぶ!! 〜キャリア戦略における計画的偶発性理論と変わる勇気の重要性〜
wanko_it
1
430
Nuances on Kubernetes - RubyConf Taiwan 2025
envek
0
140
Flutterと Vibe Coding で個人開発!
hyshu
1
240
「リーダーは意思決定する人」って本当?~ 学びを現場で活かす、リーダー4ヶ月目の試行錯誤 ~
marina1017
0
200
Scale out your Claude Code ~自社専用Agentで10xする開発プロセス~
yukukotani
9
1.8k
Gemini CLIの"強み"を知る! Gemini CLIとClaude Codeを比較してみた!
kotahisafuru
3
960
オホーツクでコミュニティを立ち上げた理由―地方出身プログラマの挑戦 / TechRAMEN 2025 Conference
lemonade_37
2
460
Google I/O Extended Incheon 2025 ~ What's new in Android development tools
pluu
1
250
Amazon Q CLI開発で学んだAIコーディングツールの使い方
licux
3
180
Featured
See All Featured
Mobile First: as difficult as doing things right
swwweet
223
9.9k
Testing 201, or: Great Expectations
jmmastey
45
7.6k
Thoughts on Productivity
jonyablonski
69
4.8k
Why Our Code Smells
bkeepers
PRO
337
57k
Done Done
chrislema
185
16k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
8
440
The Illustrated Children's Guide to Kubernetes
chrisshort
48
50k
Intergalactic Javascript Robots from Outer Space
tanoku
272
27k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.4k
A Tale of Four Properties
chriscoyier
160
23k
Docker and Python
trallard
45
3.5k
Side Projects
sachag
455
43k
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