Pontos Principais
-
O mundo moderno da programação é dividido entre profissionais altamente treinados que trabalham com código de baixo nível, e outros que não têm um conhecimento tão profundo assim, mas se concentram no desenvolvimento de aplicações de alto nível que ajudam a atender os requisitos de negócio;
-
A Ballerina é uma linguagem de programação open source, que se concentra em melhorar a produtividade dos desenvolvedores do segundo grupo, fornecendo abstrações, ferramentas e plataformas necessárias para criar aplicações nativas na nuvem;
-
A Ballerina trata a rede de maneira diferente, fazendo com que abstrações de rede, como objetos dos clientes, serviços, funções dos recursos, e listeners, façam parte do tipo de sistema da linguagem. Assim, os desenvolvedores podem usar os tipos fornecidos por ela para escrever programas de rede de maneira facilitada;
-
O sistema de rede e os tipos de dados amigável da Ballerina permite que os desenvolvedores modelem os padrões de comunicação da rede de uma maneira intuitiva permitindo assim uma maior produtividade;
-
O suporte integrado à nuvem ajuda a gerar artefatos de implantação lendo a notação definida no código-fonte. Esses artefatos podem ser Dockerfiles, imagens do Docker, arquivos Kubernetes YAML, ou funções serverless;
-
As abstrações e a sintaxe da Ballerina para simultaneidade e interação da rede foram projetadas para que haja uma correspondência próxima com os diagramas de sequência.
No início deste ano, Mike Loukides, vice-presidente de área de estratégia de conteúdo da O'Reilly Media, publicou um excelente artigo sobre "Rethinking programming", onde explora como o mundo da programação está se dividindo entre profissionais altamente treinados e pessoas que não possuem uma bagagem técnica tão aprofundada, mas dispõem de grande conhecimento na construção de sistemas e aplicações que atendem aos requisitos de negócios.
O primeiro grupo está envolvido com o empacotamento de ferramentas, frameworks, e plataformas, enquanto o segundo grupo se concentra na criação de aplicações para os negócios, integrando diferentes sistemas.
Quase todas as linguagens de programação de uso geral são projetadas com abstrações e construções para profissionais altamente treinados para resolver alguns problemas de programação de baixo nível.
A Ballerina, uma linguagem de programação open source, apresenta novas e fundamentais abstrações para ajudar os profissionais que desejam um processo ágil e usam plataformas na nuvem para escalar as aplicações com relação aos requisitos de negócios.
Como a Ballerina trata a rede
Durante décadas, as linguagens de programação trataram as redes como se fossem apenas fontes de entrada e saída de dados. Por isso, para expor APIs simples, os desenvolvedores precisam implementar esses serviços gravando um loop explícito que aguarda solicitações na rede até que um sinal seja obtido. A Ballerina trata a rede de maneira diferente, fazendo as abstrações de rede como objetos de clientes, serviços, funções dos recursos, e listeners como partes do tipo de sistema da linguagem, para que possa usar os tipos fornecidos por ela para escrever programas de rede que funcionam de maneira simples.
Usando o tipo service
com um objeto listener
na Ballerina, os desenvolvedores podem expor as APIs escrevendo apenas a lógica de negócios baseada na API na function resource
. Dependendo do protocolo definido no objeto listener
, esses serviços podem ser expostos como HTTP/HTTPS, HTTP2, gRPC e WebSockets. Os serviços da Ballerina são fornecidos com simultaneidade incorporada. Toda solicitação a um método do recurso é tratada em uma cadeia separada (unidade simultânea da Ballerina) e fornece um comportamento simultâneo que parece a de um serviço.
O seguinte código ilustra a sintaxe do tipo service
usando a Ballerina.
import ballerina/http;
service serviceName on new http:Listener(8080) {
resource function newResource(http:Caller caller, http:Request request) {
// API-led logic
}
}
No paradigma solicitação-resposta, a comunicação de rede é feita bloqueando chamadas, mas o bloqueio de mensagens de uma chamada de rede é muito custoso. Muitos frameworks de linguagem suportam invocações assíncronas, mas os desenvolvedores precisam implementar invocações do tipo chama/aguarda usando técnicas de código baseadas em callback. O trecho do código Ballerina a seguir mostra uma operação simples de HTTP GET que, para o desenvolvedor, parece uma operação de bloqueio. No entanto, internamente, ela executa uma operação assíncrona usando entrada e saída sem bloqueio, na qual o encadeamento em execução é liberado no sistema operacional para ser usado por outras operações.
http:Client clientEndpoint = new("http://example.com");
public function main() {
var response = clientEndpoint->get("/get?test=123");
-----
}
Devido ao comportamento não confiável da rede, as invocações remotas das APIs são vulneráveis a falhas. Às vezes, uma nova tentativa automática pode ajudar a recuperar essas falhas. Em alguns casos, técnicas de failover auxiliarão no fornecimento de serviços ininterruptos ou técnicas como circuit breakers podem ajudar a evitar falhas catastróficas em cascata em vários programas. Tendo essas técnicas embutidas no objeto client
da Ballerina ajuda os desenvolvedores a escrever um código resiliente e robusto com invocações na rede remota.
O seguinte trecho de código mostra como configurar um circuit breaker para lidar com erros relacionados à rede no objeto HTTP client
da Ballerina.
http:Client backendClientEP = new("http://example.com", {
circuitBreaker: {
rollingWindow: {
timeWindowInMillis: 10000,
bucketSizeInMillis: 2000,
requestVolumeThreshold: 0
},
failureThreshold: 0.2,
resetTimeInMillis: 10000,
statusCodes: [400, 404, 500]
},
timeoutInMillis: 2000
});
Os serviços de rede precisam trabalhar com várias entradas do usuário. Em geral, todas as entradas podem ser perigosas se não forem verificadas adequadamente. A análise de contaminação é projetada para aumentar a segurança, impedindo qualquer variável que possa ser modificada pela entrada do usuário. O analisador de contaminação (taint analyzer) incorporado a Ballerina ajuda a identificar dados não confiáveis (contaminados), observando como se propagam pelo programa. Se eles forem passados para um parâmetro sensível à segurança, será gerado um erro no compilador. Como a verificação de contaminação ocorre no estágio do compilador, o programador pode redesenhar o programa para aumentar a segurança em torno da entrada perigosa.
A rede e o sistema de tipos de dados amigável
A Ballerina suporta sistemas de tipos estruturais e trabalha com uma abstração de um valor chamando 'forma' (shape), um formato que basicamente ignora a identidade de armazenamento de um valor. Isso significa que não considera a referência de nome de um valor quando comparado com outros valores. Isso é útil quando quiser combinar dados de vários sistemas projetados separadamente.
A Ballerina também suporta os tipos Union
, onde os conjuntos de valores são a união de valores de tipos diferentes. Por exemplo, podemos usar uma variável do tipo union
para armazenar uma string
ou um int
, mas haverá apenas um tipo de valor em um dado momento.
A Ballerina possui suporte interno ao tipo de dados de rede, como JSON e XML. O tipo json
da Ballerina foi projetado para processar dados neste formato. É um nome interno para uma union
definida da seguinte maneira:
type json = () | boolean | int | float | decimal | string | json[] | map<json>
Por padrão, os registros da Ballerina são abertos. Vamos dar uma olhada no tipo de registro para representar os detalhes de uma pessoa.
type Gender "male"|"female";
type Address record {|
string city;
string country;
|};
type Person record {
string name;
int birthYear;
Gender gender;
Address address?;
};
Aqui, o tipo Person
é um tipo de registro aberto, definido com um "descritor de tipo de registro inclusivo" usando os delimitadores "{"
e "}"
. O tipo Address
é um tipo de registro fechado com "um descritor de tipo de registro exclusivo", usando os delimitadores "{|"
e "|}"
na definição. No registro Person
, o address
também tem o sufixo "?"
, tornando-o um campo opcional podendo ser ignorado se não for definido um valor.
Vamos criar um novo tipo, o Student
.
type Student record {
string name;
int birthYear;
Gender gender;
Address address?;
string college;
};
O tipo Student
é um subtipo do tipo Person
. Possui um campo extra college
do tipo string
comparado ao tipo Person
. Isso é possível porque o tipo Person
é um tipo aberto e suas shapes também podem ter o campo de string chamado college
.
public function main() {
Student s1 = { name: "John", birthYear: 1995, gender: "male",
college: "US Berkeley" };
Student s2 = { name: "Kamala", birthYear: 1994, gender: "female",
address: { city: "Milpitas", state: "CA" ,country: "USA"},
college: "Stanford" };
Person p1 = s1;
Person p2 = s2;
io:println("P1's details:" ,p1);
io:println("P2's details:" ,p2);
}
$ ballerina run person.bal
Compiling source
person.bal
Running executables
P1's details:name=John birthYear=1995 gender=male college=US Berkeley
P2's details:name=Kamala birthYear=1994 gender=female address=city=Milpitas state=CA country=USA college=Stanford
A consulta integrada à linguagem é um recurso que permite usar uma sintaxe única em múltiplas fontes de dados. Isso ajudará a quebrar o problema complexo em uma série de consultas curtas e compreensíveis.
As expressões de consulta da Ballerina fornecem um recurso de consulta integrado a linguagem usando a sintaxe semelhante ao SQL. Diferentemente das instruções SQL, as expressões de consulta ajudam a identificar os erros em tempo de design devido ao tipo de segurança.
import ballerina/io;
type Student record {
string name;
int age;
string school;
};
type Result record {
string name;
string college;
float gpa;
string school;
};
public function main() {
map<float> gpa = {"John": 4.1, "Bill": 3.9, "Sam": 3.3, "Jennifer": 3.1};
Student s1 = {name: "John", age: 18, school: "Milpitas High School"};
Student s2 = {name: "Bill", age: 17, school: "San Jose High School"};
Student s3 = {name: "Sam", age: 18, school: "Clara Coutinho High School"};
Student s4 = {name: "Jennifer", age: 18, school: "Fremont Union High School"};
Student[] student = [];
student.push(s1);
student.push(s2);
student.push(s3);
student.push(s4);
Result[] stanford = from var candidate in student
let float cgpa = (gpa[candidate.name] ?: 0),
string targetCollege = "Stanford"
where cgpa > 3.8
select {
name: candidate.name,
college: targetCollege,
gpa: cgpa,
school: candidate.school
};
io:println(stanford);
}
$ ballerina run query_expression.bal
Compiling source
query_expression.bal
Running executables
name=John college=Stanford GPA=4.1 school=Milpitas High School name=Bill college=Stanford GPA=3.9 school=San Jose High School
A cláusula from
funciona de maneira semelhante a uma instrução foreach
. Ela cria uma iteração a partir de um valor e, em seguida, vincula as variáveis em cada valor retornado pelo iterador. A cláusula let
liga as variáveis. A cláusula where
fornece uma maneira de realizar uma execução condicional que pode se referir a variáveis vinculadas pela cláusula from
. Quando a condição where
é avaliada como falsa, a iteração ignora as cláusulas seguintes. A cláusula select
é avaliada para cada iteração e o resultado da expressão de consulta neste exemplo é uma list
, cujos membros são o resultado da cláusula select
.
Integração de tecnologia desenvolvida na nuvem
Antes, os desenvolvedores apenas desenvolviam os programas, compilavam, e os executavam, mas atualmente eles têm várias maneiras de executar. Podem estar em um servidor físico ou virtual. Ou então, os programas podem ser empacotados como um container e implantados em plataformas Kubernetes e service mesh, ou ainda, executados como programas serverless. No entanto, essas opções de implantação não fazem parte da experiência de programação de um desenvolvedor, que precisa escrever o código de forma que funcione bem em um determinado ambiente de execução, e remover este paradigma da programação não é algo bom.
O Docker ajuda a empacotar as aplicações e suas dependências em uma imagem binária que pode ser executada em vários locais, seja localmente, em uma nuvem pública ou em uma nuvem privada. Para criar imagens otimizadas, os desenvolvedores precisam seguir um conjunto de práticas recomendadas, que caso não sejam utilizadas, a imagem será muito grande, menos segura, e com várias outras deficiências.
O compilador da Ballerina é capaz de criar imagens otimizadas do Docker a partir do código-fonte da aplicação (saiba mais em 'Docker deployment'). Adicionar o @docker:Config{}
a um service
, gera o Dockerfile e uma imagem do Docker.
import ballerina/http;
import ballerina/docker;
@docker:Config {
name: "hello",
tag: "v1.0"
}
service Hello on new http:Listener(8080) {
resource function hi(http:Caller caller, http:Request request) returns error? {
check caller->respond("Hello World!");
}
}
$ ballerina build hello.bal
Compiling source
hello.bal
Generating executables
hello.jar
Generating docker artifacts...
@docker - complete 2/2
Run the following command to start a Docker container:
docker run -d -p 8080:8080 hello:v1.0
O Kubernetes é uma plataforma open source que automatiza a implantação, o dimensionamento, e o gerenciamento de aplicações nos containers. Para implantar e executar o programa no Kubernetes, os desenvolvedores devem criar um conjunto de arquivos YAML. Para um desenvolvedor comum, criar esses arquivos não é algo fácil, mas o compilador Ballerina é capaz de criar os arquivos YAML enquanto compila o código-fonte.
import ballerina/http;
import ballerina/kubernetes;
@kubernetes:Service {
serviceType: "NodePort"
}
@kubernetes:Deployment {
name: "hello"
}
service Hello on new http:Listener(8080) {
resource function hi(http:Caller caller, http:Request request) returns error? {
check caller->respond("Hello World!");
}
}
Adicionar a notação @kubernetes:Deployment{}
ao serviço Ballerina irá gerar o YAML de Implantação do Kubernetes, que é necessário para implantar nosso "hello world" no Kubernetes, enquanto a notação @kubernetes:Service{}
gerará o YAML do serviço Kubernetes. Nesse cenário, definimos serviceType
como `NodePort`
para acessar o serviço do "hello world" através do nodeIP:Port.
$ ballerina build hello.bal
Compiling source
hello.bal
Generating executables
hello.jar
Generating artifacts...
@kubernetes:Service - complete 1/1
@kubernetes:Deployment - complete 1/1
@kubernetes:Docker - complete 2/2
@kubernetes:Helm - complete 1/1
Run the following command to deploy the Kubernetes artifacts:
kubectl apply -f hello/kubernetes
Run the following command to install the application using Helm:
helm install --name helloworld hello/kubernetes/helloworld
Da mesma forma, os desenvolvedores podem usar as notações nativas do Ballerina para implantar programas em plataformas como OpenShift, Istio e Knative, ou como funções do AWS Lambda.
Diagrama de sequência
Um diagrama de sequência é a melhor maneira de descrever visualmente como os serviços interagem. As abstrações e a sintaxe da Ballerina para simultaneidade e interação de rede foram projetadas para que haja uma correspondência próxima com os diagramas de sequência. Na Ballerina, um método remoto é chamado usando uma sintaxe diferente (->) de um método não remoto. É representada como uma seta horizontal da linha de vida de uma chamada (Worker) até a linha de vida do objeto do cliente em um diagrama de sequência.
Em geral, os desenvolvedores escrevem o código uma vez, e o lêem diversas vezes. Em muitos casos, o código é lido por outro desenvolvedor e não pelo autor. Ter essas representações visuais automatizadas ajudará os desenvolvedores a entender as interações do programa.
O plug-in na IDE da Ballerina (por exemplo, o plug-in VSCode) pode gerar um diagrama de sequência dinamicamente a partir do código-fonte.
Leitura adicional
Neste artigo, vimos como os recursos exclusivos da Ballerina permitem que os desenvolvedores criem aplicações nativas na nuvem.
Para princípios de design de linguagem mais abrangentes, consulte a documentação da linguagem Ballerina.
Vários exemplos de uso de tipos de dados incorporados, como JSON/XML e outras funcionalidades baseadas em rede podem ser encontrados nas páginas Ballerina by Example.
Sobre o autor
Lakmal Warusawithana é o desenvolvedor sênior e diretor de relações do WSO2. Lakmal tem uma longa história de trabalho em tecnologias open source, na nuvem e em DevOps, além de ter sido vice-presidente do Projeto Apache Stratos PaaS. Lakmal é um arquiteto de orquestração de container e implantação da Ballerina, uma linguagem de programação open source para aplicações distribuídas na rede. Lakmal também fez apresentações em vários eventos, incluindo ApacheCon, CloudOpen, QCon, JaxLondon, Cloud Expo, Cloudstack Collaboration Conference, WSO2Con, KubeCon, ContainerCamp, DeveloperWeek, API Summit e muitos outros encontros de tecnologia.