Microsserviços com o Oracle Helidon

Microsserviços com o Oracle Helidon

1. Visão geral

Helidon é a nova estrutura de microsserviço Java que foi disponibilizada recentemente pela Oracle. Foi usado internamente em projetos Oracle com o nome J4C (Java for Cloud).

Neste tutorial, cobriremos os principais conceitos da estrutura e, em seguida, passaremos para construir e executar um microsserviço baseado em Helidon.

2. Modelo de Programação

Atualmente,the framework supports two programming models for writing microservices: Helidon SE and Helidon MP.

Enquanto o Helidon SE foi projetado para ser uma microframework que suporta o modelo de programação reativa, o Helidon MP, por outro lado, é um tempo de execução do Eclipse MicroProfile que permite que a comunidade Java EE execute microsserviços de maneira portátil.

Em ambos os casos, um microsserviço Helidon é um aplicativo Java SE que inicia um servidor HTTP minúsculo a partir do método principal.

3. Helidon SE

Nesta seção, descobriremos com mais detalhes os principais componentes do Helidon SE: WebServer, Config e Security.

3.1. Configurando o WebServer

Para começar comWebServer API,, precisamos adicionar oMaven dependency necessário ao arquivopom.xml:


    io.helidon.webserver
    helidon-webserver
    0.10.4

Para ter um aplicativo web simples,we can use one of the following builder methods: WebServer.create(serverConfig, routing) or just WebServer.create(routing). O último utiliza uma configuração padrão do servidor, permitindo que o servidor seja executado em uma porta aleatória.

Este é um aplicativo da Web simples que é executado em uma porta predefinida. Também registramos um manipulador simples que responderá com mensagem de saudação a qualquer solicitação HTTP com caminho ‘/greet' e MétodoGET:

public static void main(String... args) throws Exception {
    ServerConfiguration serverConfig = ServerConfiguration.builder()
      .port(9001).build();
    Routing routing = Routing.builder()
      .get("/greet", (request, response) -> response.send("Hello World !")).build();
    WebServer.create(serverConfig, routing)
      .start()
      .thenAccept(ws ->
          System.out.println("Server started at: http://localhost:" + ws.port())
      );
}

A última linha é iniciar o servidor e aguardar o atendimento de solicitações HTTP. Mas se executarmos este código de amostra no método principal, obteremos o erro:

Exception in thread "main" java.lang.IllegalStateException:
  No implementation found for SPI: io.helidon.webserver.spi.WebServerFactory

OWebServer é na verdade um SPI e precisamos fornecer uma implementação de tempo de execução. Atualmente,Helidon provides the NettyWebServer implementation que é baseado noNetty Core.

Aqui está oMaven dependency para esta implementação:


    io.helidon.webserver
    helidon-webserver-netty
    0.10.4
    runtime

Agora, podemos executar o aplicativo principal e verificar se ele funciona chamando o terminal configurado:

http://localhost:9001/greet

Neste exemplo, configuramos a porta e o caminho usando o padrão do construtor.

O Helidon SE também permite usar um padrão de configuração onde os dados de configuração são fornecidos pela APIConfig. Esse é o assunto da próxima seção.


3.2. A APIConfig

The Config API provides tools for reading configuration data from a configuration source.

O Helidon SE fornece implementações para muitas fontes de configuração. A implementação padrão é fornecida porhelidon-config, onde a fonte de configuração é um arquivoapplication.properties localizado no classpath:


    io.helidon.config
    helidon-config
    0.10.4

Para ler os dados de configuração, só precisamos usar o construtor padrão que, por padrão, obtém os dados de configuração deapplication.properties:

Config config = Config.builder().build();

Vamos criar um arquivoapplication.properties no diretóriosrc/main/resource com o seguinte conteúdo:

server.port=9080
web.debug=true
web.page-size=15
user.home=C:/Users/app

To read the values we can use the Config.get() method seguido por uma conversão conveniente para os tipos Java correspondentes:

int port = config.get("server.port").asInt();
int pageSize = config.get("web.page-size").asInt();
boolean debug = config.get("web.debug").asBoolean();
String userHome = config.get("user.home").asString();

Na verdade, o construtor padrão carrega o primeiro arquivo encontrado nesta ordem de prioridade:application.yaml, application.conf, application.json, and application.properties. Os três últimos formatos precisam de uma dependência de configuração relacionada extra. Por exemplo, para usar o formato YAML, precisamos adicionar a dependência YAMLconfig relacionada:


    io.helidon.config
    helidon-config-yaml
    0.10.4

E então, adicionamos umapplication.yml:

server:
  port: 9080
web:
  debug: true
  page-size: 15
user:
  home: C:/Users/app

Da mesma forma, para usar o CONF, que é um formato simplificado JSON, ou formatos JSON, precisamos adicionar a dependênciahelidon-config-hocon.

Observe que os dados de configuração nesses arquivos podem ser substituídos por variáveis ​​de ambiente e propriedades do sistema Java.

We can also control the default builder behavior desativando a variável de ambiente e as propriedades do sistema ou especificando explicitamente a fonte de configuração:

ConfigSource configSource = ConfigSources.classpath("application.yaml").build();
Config config = Config.builder()
  .disableSystemPropertiesSource()
  .disableEnvironmentVariablesSource()
  .sources(configSource)
  .build();

Além de ler os dados de configuração do caminho de classe, também podemos usar duas configurações de fontes externas, ou seja, as configurações git e etcd. Para isso, precisamos das dependênciashelidon-config-gitehelidon-git-etcd.

Finalmente, se todas essas fontes de configuração não satisfizerem nossa necessidade, Helidon nos permite fornecer uma implementação para nossa fonte de configuração. Por exemplo, podemos fornecer uma implementação que pode ler os dados de configuração de um banco de dados.

3.3. A APIRouting

The Routing API provides the mechanism by which we bind HTTP requests to Java methods. Podemos fazer isso usando o método de solicitação e o caminho como critérios de correspondência ou o objetoRequestPredicate para usar mais critérios.

Portanto, para configurar uma rota, podemos apenas usar o método HTTP como critério:

Routing routing = Routing.builder()
  .get((request, response) -> {} );

Ou podemos combinar o método HTTP com o caminho da solicitação:

Routing routing = Routing.builder()
  .get("/path", (request, response) -> {} );

Também podemos usarRequestPredicate para obter mais controle. Por exemplo, podemos verificar um cabeçalho existente ou o tipo de conteúdo:

Routing routing = Routing.builder()
  .post("/save",
    RequestPredicate.whenRequest()
      .containsHeader("header1")
      .containsCookie("cookie1")
      .accepts(MediaType.APPLICATION_JSON)
      .containsQueryParameter("param1")
      .hasContentType("application/json")
      .thenApply((request, response) -> { })
      .otherwise((request, response) -> { }))
      .build();

Até agora, fornecemos manipuladores no estilo funcional. Também podemos usar a classeService que permite escrever manipuladores de uma maneira mais sofisticada.

Então, vamos primeiro criar um modelo para o objeto com o qual estamos trabalhando, a classeBook:

public class Book {
    private String id;
    private String name;
    private String author;
    private Integer pages;
    // ...
}

Podemos criar serviços REST para a classeBook implementando o métodoService.update(). Isso permite configurar os subcaminhos do mesmo recurso:

public class BookResource implements Service {

    private BookManager bookManager = new BookManager();

    @Override
    public void update(Routing.Rules rules) {
        rules
          .get("/", this::books)
          .get("/{id}", this::bookById);
    }

    private void bookById(ServerRequest serverRequest, ServerResponse serverResponse) {
        String id = serverRequest.path().param("id");
        Book book = bookManager.get(id);
        JsonObject jsonObject = from(book);
        serverResponse.send(jsonObject);
    }

    private void books(ServerRequest serverRequest, ServerResponse serverResponse) {
        List books = bookManager.getAll();
        JsonArray jsonArray = from(books);
        serverResponse.send(jsonArray);
    }
    //...
}

Também configuramos o tipo de mídia como JSON, portanto, precisamos da dependênciahelidon-webserver-json para esta finalidade:


    io.helidon.webserver
    helidon-webserver-json
    0.10.4

Finalmente,we use the register() method of the Routing builder to bind the root path to the resource. Neste caso,Paths configurados pelo serviço são prefixados pelo caminho raiz:

Routing routing = Routing.builder()
  .register(JsonSupport.get())
  .register("/books", new BookResource())
  .build();

Agora podemos iniciar o servidor e verificar os pontos de extremidade:

http://localhost:9080/books
http://localhost:9080/books/0001-201810

3.4. Segurança

Nesta seção,we’re going to secure our resources using the Security module.

Vamos começar declarando todas as dependências necessárias:


    io.helidon.security
    helidon-security
    0.10.4


    io.helidon.security
    helidon-security-provider-http-auth
    0.10.4


    io.helidon.security
    helidon-security-integration-webserver
    0.10.4

O módulo de segurança oferece muitos provedores para autenticação e autorização. For this example, we’ll use the HTTP basic authentication provider, pois é bastante simples, mas o processo para outros provedores é quase o mesmo.

The first thing to do is create a Security instance. We can do it either programmatically for simplicity:

Map users = //...
UserStore store = user -> Optional.ofNullable(users.get(user));

HttpBasicAuthProvider httpBasicAuthProvider = HttpBasicAuthProvider.builder()
  .realm("myRealm")
  .subjectType(SubjectType.USER)
  .userStore(store)
  .build();

Security security = Security.builder()
  .addAuthenticationProvider(httpBasicAuthProvider)
  .build();

Ou podemos usar uma abordagem de configuração.

Nesse caso, declararemos todas as configurações de segurança no arquivoapplication.yml que carregamos por meio da APIConfig:

#Config 4 Security ==> Mapped to Security Object
security:
  providers:
  - http-basic-auth:
      realm: "helidon"
      principal-type: USER # Can be USER or SERVICE, default is USER
      users:
      - login: "user"
        password: "user"
        roles: ["ROLE_USER"]
      - login: "admin"
        password: "admin"
        roles: ["ROLE_USER", "ROLE_ADMIN"]

  #Config 4 Security Web Server Integration ==> Mapped to WebSecurity Object
  web-server:
    securityDefaults:
      authenticate: true
    paths:
    - path: "/user"
      methods: ["get"]
      roles-allowed: ["ROLE_USER", "ROLE_ADMIN"]
    - path: "/admin"
      methods: ["get"]
      roles-allowed: ["ROLE_ADMIN"]

E para carregá-lo, precisamos apenas criar um objetoConfig e então invocar o métodoSecurity.fromConfig():

Config config = Config.create();
Security security = Security.fromConfig(config);

Assim que tivermos a instânciaSecurity, primeiro precisamos registrá-la comWebServer usando o métodoWebSecurity.from():

Routing routing = Routing.builder()
  .register(WebSecurity.from(security).securityDefaults(WebSecurity.authenticate()))
  .build();

Também podemos criar uma instânciaWebSecurity diretamente usando a abordagem de configuração pela qual carregamos a segurança e a configuração do servidor web:

Routing routing = Routing.builder()
  .register(WebSecurity.from(config))
  .build();

Agora podemos adicionar alguns manipuladores para os caminhos/usere/admin, iniciar o servidor e tentar acessá-los:

Routing routing = Routing.builder()
  .register(WebSecurity.from(config))
  .get("/user", (request, response) -> response.send("Hello, I'm Helidon SE"))
  .get("/admin", (request, response) -> response.send("Hello, I'm Helidon SE"))
  .build();

4. Helidon MP

Helidon MP is an implementation of Eclipse MicroProfilee também fornece um tempo de execução para a execução de microsserviços baseados em MicroProfile.

Como já temosan article about Eclipse MicroProfile, vamos verificar o código-fonte e modificá-lo para rodar no Helidon MP.

Depois de verificar o código, removeremos todas as dependências e plug-ins e adicionaremos as dependências do Helidon MP ao arquivo POM:


    io.helidon.microprofile.bundles
    helidon-microprofile-1.2
    0.10.4


    org.glassfish.jersey.media
    jersey-media-json-binding
    2.26

As dependênciashelidon-microprofile-1.2ejersey-media-json-binding estão disponíveis no Maven Central.

A seguir,we’ll add the beans.xml file under the src/main/resource/META-INF directory com este conteúdo:


Na classeLibraryApplication, substitua o métodogetClasses() para que o servidor não verifique os recursos:

@Override
public Set> getClasses() {
    return CollectionsHelper.setOf(BookEndpoint.class);
}

Por fim, crie um método principal e adicione este trecho de código:

public static void main(String... args) {
    Server server = Server.builder()
      .addApplication(LibraryApplication.class)
      .port(9080)
      .build();
    server.start();
}

E é isso. Agora seremos capazes de invocar todos os recursos do livro.

5. Conclusão

Neste artigo, exploramos os principais componentes do Helidon, também mostrando como configurar o Helidon SE e MP. Como o Helidon MP é apenas um tempo de execução do Eclipse MicroProfile, podemos executar qualquer microsserviço baseado em MicroProfile existente usando-o.

Como sempre, o código de todos os exemplos acima pode ser encontradoover on GitHub.