BT

Início Artigos O que há de novo no Jakarta NoSQL? Parte 2: O que é cloud-native?

O que há de novo no Jakarta NoSQL? Parte 2: O que é cloud-native?

Favoritos

Pontos Principais

  • Baseado em diversas definições sobre o assunto, podemos dizer que cloud-native é um termo usado para descrever ambientes baseados em containers.
  • Quais são as boas práticas a serem utilizadas no desenvolvimento de uma solução cloud-native?
  • Confira o código de exemplo de uma aplicação cloud-native e saiba como rodá-la em uma ambiente PaaS.

No primeiro post dessa série, exploramos o funcionamento do Jakarta NoSQL, uma das grandes novidades da plataforma Jakarta EE . Neste segundo post, falaremos sobre o que é o cloud-native e como criar a aplicação baseada nesses princípios, utilizando o Jakarta EE e uma solução PaaS que facilita essa integração.

Mudando para o cloud-native

A computação em nuvem trouxe muitas metodologias e técnicas que revolucionaram o mundo técnico e comercial. Entre os novos termos cunhados, há o cloud-native. Para atender e estar a par das expectativas no universo Java, criou-se o Jakarta EE. O objetivo deste artigo é comentar sobre o conceito do cloud-native e executar uma aplicação usando-o juntamente com a versão mais recente do Jakarta EE NoSQL.

O que é o cloud-native?

Como qualquer conceito novo, existem diversas definições com o mesmo nome. Se lermos livros ou artigos sobre cloud-native poderemos não encontrar um consenso sobre o termo. Por exemplo:

Cloud-native é uma abordagem para criar e executar aplicações que explora as vantagens do modelo de computação em nuvem.

From Pivotal

Cloud-native é uma modo diferente de pensar e raciocinar sobre sistemas de software. Incorpora os seguintes conceitos: Produzido por uma infraestrutura descartável, composta de limites, globalmente escalável, dotada de uma arquitetura descartável.

Architecting Cloud Native Applications: Design high-performing and cost-effective applications for the cloud

Em uso geral, "cloud-native" é uma abordagem para criar e executar aplicações que exploram as vantagens do modelo de entrega baseados na computação em nuvem. "Cloud-native" é sobre como, e não onde, as aplicações são criados e implantadas.

InfoWorld

Em um entendimento baseado nas definições de diversos artigos, podemos dizer que cloud-native é um termo usado para descrever ambientes baseados em containers. Portanto, não está relacionado a linguagens ou estruturas de programação específicas, ou mesmo a uma empresa provedora de nuvem, mas a containers.

Quais são as melhores práticas quando falamos sobre cloud-native?

Quando começamos a aprender um novo conceito, normalmente buscamos ler sobre suas melhores práticas, a fim de evitar erros e qualquer code smell (problemas que podem ocorrer devido o não conhecimento das práticas e design). Com a Programação Orientada a Objetos (OOP), temos os padrões de design da Gang dos Quatro, em Java, temos o Java Efetivo e, ao falar sobre arquitetura, temos o Código Limpo e a Arquitetura Limpa. Portanto, a pergunta é: Quais são as melhores práticas para o cloud-native?

Não existem práticas recomendadas relacionadas especificamente ao nativo da nuvem, ao menos até onde sabemos. Mas como a nuvem está próxima da metodologia Agile, há várias práticas que podemos utilizar para termos uma aplicação saudável e indolor, além das citadas anteriormente:

As práticas mais conhecidas relacionadas a qualquer aplicação que inclua computação em nuvem são inspiradas nos Patterns of Enterprise Application Architecture e Refactoring, de Martin Fowler.

A Aplicação Doze Fatores

  1. Codebase: Uma base de código com rastreamento utilizando controle de revisão, vários deploys;
  2. Dependencies: Declare e isole as dependências;
  3. Config: Armazene as configurações no ambiente;
  4. Backing services: Trate os serviços de apoio, como recursos interligados;
  5. Build, release, run: Separe estritamente os builds e execute-os em etapas;
  6. Processes: Execute a aplicação como um ou mais processos que são stateless;
  7. Port binding: Exporte serviços usando port binding;
  8. Concurrency: Dimensione usando um modelo de processo;
  9. Disposability: Maximize a robustez com inicialização e desligamento rápido;
  10. Dev/prod parity: Mantenha os ambientes de desenvolvimento, teste, produção o mais semelhante possível;
  11. Logs: Trate os logs como fluxo de eventos;
  12. Admin processes: Execute tarefas de administração/gerenciamento como processos pontuais;

Em resumo, ainda não existem práticas recomendadas mais específicas para o cloud-native, mas existem padrões do Agile, microservices e a aplicação de doze fatores que são importantes, e podem ser seguidas.

De volta ao código

Na introdução, explicamos com detalhes, o que significa cloud-native. Agora vamos retornar a nossa aplicação e convertê-la para uma aplicação cloud-native. No primeiro artigo, explicamos o modelo, a entidade e como o Jakarta NoSQL funciona. Portanto, usaremos a maneira mais fácil de lidar com as consultas com NoSQL e MongoDB com um Repository.

import jakarta.nosql.mapping.Column;
import jakarta.nosql.mapping.Entity;
import jakarta.nosql.mapping.Id;

import javax.json.bind.annotation.JsonbVisibility;
import java.io.Serializable;
import java.util.Objects;
import java.util.Set;

@Entity
@JsonbVisibility(FieldPropertyVisibilityStrategy.class)
public class Hero implements Serializable {

	@Id
	private String name;

	@Column
	private Integer age;

	@Column
	private Set<String> powers;

}
import jakarta.nosql.mapping.Page;
import jakarta.nosql.mapping.Pagination;
import jakarta.nosql.mapping.Repository;

import java.util.stream.Stream;

public interface HeroRepository extends Repository<Hero, String> {

	Stream<Hero> findAll();

	Page<Hero> findAll(Pagination pagination);

	Stream<Hero> findByPowersIn(String powers);

	Stream<Hero> findByAgeGreaterThan(Integer age);

	Stream<Hero> findByAgeLessThan(Integer age);
}

Para disponibilizar os serviços, criaremos uma aplicação REST com JAX-RS como sendo uma classe de recurso.

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
import java.util.function.Supplier;

import static java.util.stream.Collectors.toList;

@ApplicationScoped
@Path("heroes")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class HeroResource {

	private static final Supplier<WebApplicationException> NOT_FOUND =
        	() -> new WebApplicationException(Response.Status.NOT_FOUND);

	@Inject
	private HeroRepository repository;

	@GET
	public List<Hero> findAll() {
    	return repository.findAll()
            	.collect(toList());
	}

	@GET
	@Path("/{id}")
	public Hero findById(@PathParam("id") String id) {
    	return repository.findById(id).orElseThrow(NOT_FOUND);
	}

	@GET
	@Path("seniors/{age}")
	public List<Hero> findByOlder(@PathParam("age") Integer age) {
    	return repository.findByAgeGreaterThan(age)
            	.collect(toList());
	}

	@GET
	@Path("youngs/{age}")
	public List<Hero> findByYounger(@PathParam("age") Integer age) {
    	return repository.findByAgeLessThan(age)
            	.collect(toList());
	}

	@POST
	public void save(Hero hero) {
    	repository.save(hero);
	}

	@PUT
	@Path("/{id}")
	public void update(@PathParam("id") String id, Hero hero) {
    	repository.save(hero);
	}

	@Path("/{id}")
	@DELETE
	public void delete(@PathParam("id") String name) {
    	repository.deleteById(name);
	}
}

A aplicação está pronta. A última etapa, que criaremos é a classe de configuração que permite a conexão com o MongoDB. É bem simples. Usaremos o Eclipse MicroProfile Configuration que possui recursos com o Eclipse JNoSQL, a implementação de referência do Jakarta NoSQL. O Eclipse MicroProfile Config é uma solução para externalizar a configuração das aplicações Java e facilita o acompanhamento do terceiro fator.

import jakarta.nosql.document.DocumentCollectionManager;
import org.eclipse.microprofile.config.inject.ConfigProperty;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;

@ApplicationScoped
class DocumentManagerProducer {

	@Inject
	@ConfigProperty(name = "document")
	private DocumentCollectionManager manager;

	@Produces
	public DocumentCollectionManager getManager() {
    	return manager;
	}

	public void destroy(@Disposes DocumentCollectionManager manager) {
    	manager.close();
	}
}

A configuração atual de uma aplicação pode ser acessada via ConfigProvider#getConfig().

Um Config consiste nas informações coletadas dos org.eclipse.microprofile.config.spi.ConfigSources registrados. Esses ConfigSource são classificados de acordo com seu ordinal. Dessa forma, podemos substituir a configuração com menor importância externamente.

Por padrão, existem 3 ConfigSources:

  • System.getProperties() (ordinal=400);
  • System.getenv() (ordinal=300);
  • Todos os arquivos META-INF/microprofile-config.properties no ClassPath. (ordinal padrão=100, configurável separadamente por meio de uma propriedade config_ordinal dentro de cada arquivo).

Portanto, os valores padrão podem ser especificados nos arquivos acima fornecidos com a aplicação e o valor pode ser substituído posteriormente para cada implantação. Um número ordinal mais alto tem preferência sobre um número mais baixo.

Isso implica que podemos ter a configuração do ambiente local como um arquivo, uma para teste, também como um arquivo e, podemos substituir todas essas informações quando as movermos para a nuvem.

document=document
document.database=conferences
document.settings.jakarta.nosql.host=localhost:27017
document.provider=org.eclipse.jnosql.diana.mongodb.document.MongoDBDocumentConfiguration

Agora temos uma configuração local, então vamos mudar nossa aplicação com o Jakarta EE com base na abordagem cloud-native. Para facilitar, usaremos uma PaaS (Plataforma como Serviço) porque podemos mover a aplicação baseada em container da aplicação da nuvem através da infraestrutura como código (IaC).

A infraestrutura como código, ou infraestrutura programável, nada mais é do que escrever um código, que pode ser feito usando uma linguagem de alto nível ou qualquer linguagem descritiva para gerenciar configurações e automatizar o provisionamento da infraestrutura, além das implantações.

Estrutura do Platform.sh

A aplicação Java está pronta para ser usada! A próxima etapa é definir os arquivos Platform.sh necessários para gerenciar e implantá-la. Em nosso primeiro artigo sobre o Java, analisamos detalhadamente cada um desses três arquivos:

  • Um roteador (.platform / routes.yaml). O Platform.sh permite definir as rotas;
  • Zero ou mais containers de serviço (.platform/services.yaml). O Platform.sh permite definir e configurar completamente a topologia e os serviços que desejamos utilizar no nosso projeto;
  • Um ou mais containers de aplicações (.platform.app.yaml). Controlamos a aplicação e a maneira como será criada e implantada no Platform.sh por meio de um único arquivo de configuração.

O arquivo que será alterado nesta postagem é o arquivo de serviço, permitindo definir um banco de dados, mecanismo de pesquisa, cache e assim por diante. Para este projeto, vamos usar o MongoDB ao invés do MySQL.

mongodb:
	type: mongodb:3.6
	disk: 1024

Para ler a configuração do ambiente, o Platform.sh usa o leitor de configuração que permite fácil integração. O Platform.sh também suporta estruturas e linguagens de matriz, incluindo o Java. Neste artigo, substituiremos a configuração do MongoDB pelas propriedades do Java que irão adicionar transparência a aplicação, graças à configuração do MicroProfile do Eclipse. Com esses arquivos prontos e enviados, o Platform.sh criará um conjunto de containers em um cluster.

# Este arquivo descreve uma aplicação. Pode ter várias aplicações
# no mesmo projeto.
#
# Consulte https://docs.platform.sh/user_guide/reference/platform-app-yaml.html

# O nome da aplicação. Deve ser exclusivo dentro de um projeto.

name: app

# A versão que a aplicação utiliza.
type: "java:8"

disk: 800

# Os hooks executados por vários pontos no ciclo de vida da aplicação.
hooks:
  build: |
	wget https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64
	mv jq-linux64 jq
	chmod +x jq
	mvn -U -DskipTests clean package payara-micro:bundle

# Os relacionamentos da aplicação com serviços ou demais aplicações.
#
# O lado esquerdo é o nome do relacionamento, pois será exposto
# para a aplicação na variável PLATFORM_RELATIONSHIPS. O lado direito
# está no formato ‘<nome do serviço>: <nome do terminal>’.

relationships:
  mongodb: 'mongodb:mongodb'

# A configuração da aplicação quando é exposta na web.
web:
  commands:
	start: |
  	export MONGO_PORT=`echo $PLATFORM_RELATIONSHIPS|base64 -d|json_pp|./jq -r ".mongodb[0].port"`
  	export MONGO_HOST=`echo $PLATFORM_RELATIONSHIPS|base64 -d|json_pp|./jq -r ".mongodb[0].host"`
  	export MONGO_ADDRESS="${MONGO_HOST}:${MONGO_PORT}"
  	export MONGO_PASSWORD=`echo $PLATFORM_RELATIONSHIPS|base64 -d|json_pp|./jq -r ".mongodb[0].password"`
  	export MONGO_USER=`echo $PLATFORM_RELATIONSHIPS|base64 -d|json_pp|./jq -r ".mongodb[0].username"`
  	export MONGO_DATABASE=`echo $PLATFORM_RELATIONSHIPS|base64 -d|json_pp|./jq -r ".mongodb[0].path"`
  	java -jar -Xmx1024m -Ddocument.settings.jakarta.nosql.host=$MONGO_ADDRESS \
  	-Ddocument.database=$MONGO_DATABASE -Ddocument.settings.jakarta.nosql.user=$MONGO_USER \
  	-Ddocument.settings.jakarta.nosql.password=$MONGO_PASSWORD \
  	-Ddocument.settings.mongodb.authentication.source=$MONGO_DATABASE \
  	target/heroes-microbundle.jar --port $PORT

A aplicação está pronta. Agora vamos movê-la para a nuvem com o Platform.sh usando as seguintes etapas:

  • Crie uma conta trial gratuita.
  • Inscreva-se com um usuário e senha ou faça login usando sua conta atual do GitHub, Bitbucket ou Google. Se usarmos um login de terceiros, poderemos definir uma senha para a conta Platform.sh posteriormente;
  • Selecione a região do mundo em que nosso site deve residir;
  • Selecione o modelo em branco.

Após esse passo a passo, o Platform.sh fornecerá toda a infraestrutura para nós e oferecerá também ao projeto um repositório remoto do Git. A infraestrutura orientada a Platform.sh Git significa que gerenciará automaticamente tudo o que a aplicação precisa para enviá-lo ao repositório remoto principal. Depois de configurar as chaves SSH, só precisamos escrever o código, incluindo alguns arquivos YAML que especificam a infraestrutura desejada, depois enviá-lo para o Git e depois dar o push.

git remote add platform <platform.sh@gitrepository>
git commit -m "Initial project"
git push -u platform master

Nesta postagem, falamos sobre os princípios e as melhores práticas em torno do cloud-native, que ainda é uma área que precisa ser aprimorada quando falamos sobre novas técnicas de desenvolvimento de software. A nuvem facilita o desenvolvimento de software, e podemos ver uma aplicação sendo executada através do Platform.sh e integrada ao Jakarta EE. Tudo isso para mostrar que é um ótimo momento para mudarmos nosso projeto para um PaaS na nuvem, assim como o Platform.sh.

Avalie esse artigo

Relevância
Estilo/Redação

Olá visitante

Você precisa cadastrar-se no InfoQ Brasil ou para enviar comentários. Há muitas vantagens em se cadastrar.

Obtenha o máximo da experiência do InfoQ Brasil.

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Comentários da comunidade

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

BT

Seu cadastro no InfoQ está atualizado? Poderia rever suas informações?

Nota: se você alterar seu email, receberá uma mensagem de confirmação

Nome da empresa:
Cargo/papel na empresa:
Tamanho da empresa:
País:
Estado:
Você vai receber um email para validação do novo endereço. Esta janela pop-up fechará em instantes.