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
スタートアップの急成長を支えるプラットフォームエンジニアリングと組織戦略
sutochin26
1
6.7k
「テストは愚直&&網羅的に書くほどよい」という誤解 / Test Smarter, Not Harder
munetoshi
0
190
CDK引数設計道場100本ノック
badmintoncryer
2
370
Flutterで備える!Accessibility Nutrition Labels完全ガイド
yuukiw00w
0
170
Claude Code + Container Use と Cursor で作る ローカル並列開発環境のススメ / ccc local dev
kaelaela
12
6.8k
GPUを計算資源として使おう!
primenumber
1
200
Git Sync を超える!OSS で実現する CDK Pull 型デプロイ / Deploying CDK with PipeCD in Pull-style
tkikuc
4
250
ご注文の差分はこちらですか? 〜 AWS CDK のいろいろな差分検出と安全なデプロイ
konokenj
3
480
High-Level Programming Languages in AI Era -Human Thought and Mind-
hayat01sh1da
PRO
0
840
AI駆動のマルチエージェントによる業務フロー自動化の設計と実践
h_okkah
0
210
Startups on Rails in Past, Present and Future–Irina Nazarova, RailsConf 2025
irinanazarova
0
200
dbt民主化とLLMによる開発ブースト ~ AI Readyな分析サイクルを目指して ~
yoshyum
3
1.1k
Featured
See All Featured
Building Applications with DynamoDB
mza
95
6.5k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
357
30k
Unsuck your backbone
ammeep
671
58k
Making the Leap to Tech Lead
cromwellryan
134
9.4k
The Illustrated Children's Guide to Kubernetes
chrisshort
48
50k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
130
19k
RailsConf 2023
tenderlove
30
1.1k
Typedesign – Prime Four
hannesfritz
42
2.7k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
248
1.3M
Thoughts on Productivity
jonyablonski
69
4.7k
Side Projects
sachag
455
42k
Embracing the Ebb and Flow
colly
86
4.7k
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 <everont@azukiapp.com>", "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 everton@azukiapp.com Sponsors