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
AI Agent 時代のソフトウェア開発を支える AWS Cloud Development Kit (CDK)
konokenj
5
650
Quand Symfony, ApiPlatform, OpenAI et LangChain s'allient pour exploiter vos PDF : de la théorie à la production…
ahmedbhs123
0
210
PHPで始める振る舞い駆動開発(Behaviour-Driven Development)
ohmori_yusuke
2
420
Rails Frontend Evolution: It Was a Setup All Along
skryukov
0
240
Google Agent Development Kit でLINE Botを作ってみた
ymd65536
2
260
20250704_教育事業におけるアジャイルなデータ基盤構築
hanon52_
5
910
初学者でも今すぐできる、Claude Codeの生産性を10倍上げるTips
s4yuba
16
12k
すべてのコンテキストを、 ユーザー価値に変える
applism118
4
1.4k
猫と暮らす Google Nest Cam生活🐈 / WebRTC with Google Nest Cam
yutailang0119
0
160
VS Code Update for GitHub Copilot
74th
2
670
Agentic Coding: The Future of Software Development with Agents
mitsuhiko
0
120
High-Level Programming Languages in AI Era -Human Thought and Mind-
hayat01sh1da
PRO
0
840
Featured
See All Featured
YesSQL, Process and Tooling at Scale
rocio
173
14k
Testing 201, or: Great Expectations
jmmastey
43
7.6k
Raft: Consensus for Rubyists
vanstee
140
7k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
107
19k
Designing Experiences People Love
moore
142
24k
The Straight Up "How To Draw Better" Workshop
denniskardys
235
140k
The Invisible Side of Design
smashingmag
301
51k
Embracing the Ebb and Flow
colly
86
4.7k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
251
21k
Stop Working from a Prison Cell
hatefulcrawdad
271
21k
Building a Scalable Design System with Sketch
lauravandoore
462
33k
Fashionably flexible responsive web design (full day workshop)
malarkey
407
66k
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