Микроуслуги с Oracle Helidon

1. Общ преглед

Helidon е новата Java микросервисна рамка, която е отворена наскоро от Oracle. Той беше използван вътрешно в проекти на Oracle под името J4C (Java за облак).

В този урок ще разгледаме основните концепции на рамката и след това ще преминем към изграждане и стартиране на микрослужба, базирана на Helidon.

2. Модел за програмиране

В момента рамката поддържа два програмни модела за писане на микроуслуги: Helidon SE и Helidon MP.

Докато Helidon SE е проектиран да бъде микрорамка, която поддържа модела на реактивно програмиране, Helidon MP, от друга страна, е изпълнение на Eclipse MicroProfile, което позволява на общността на Джакарта EE да изпълнява микроуслуги по преносим начин.

И в двата случая микрослужбата Helidon е приложение на Java SE, което стартира консервиран HTTP сървър от основния метод.

3. Хелидон SE

В този раздел ще открием по-подробно основните компоненти на Helidon SE: WebServer, Config и Security.

3.1. Настройване на WebServer

За да започнем с WebServer API , трябва да добавим необходимата зависимост на Maven към файла pom.xml :

 io.helidon.webserver helidon-webserver 0.10.4 

За да имаме просто уеб приложение, можем да използваме един от следните методи за изграждане: WebServer.create (serverConfig, маршрутизация) или просто WebServer.create (маршрутизиране) . Последният взема конфигурация на сървъра по подразбиране, позволяваща на сървъра да работи на произволен порт.

Ето просто уеб приложение, което работи на предварително дефиниран порт. Също така сме регистрирали прост манипулатор, който ще отговори с поздравително съобщение за всяка HTTP заявка с '/ greet' път и GET метод:

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: //localhost:" + ws.port()) ); }

Последният ред е да стартирате сървъра и да изчакате обслужването на HTTP заявки. Но ако стартираме този примерен код в основния метод, ще получим грешката:

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

В уеб сървър е всъщност SPI, и ние трябва да се осигури изпълнението по време на работа. В момента Helidon предоставя реализацията на NettyWebServer, която се основава на Netty Core.

Ето зависимостта на Maven за това изпълнение:

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

Сега можем да стартираме основното приложение и да проверим дали работи, като извикаме конфигурираната крайна точка:

//localhost:9001/greet

В този пример конфигурирахме както порта, така и пътя, използвайки шаблона на конструктора.

Helidon SE позволява използване на модел конфигурационния където данните за конфигурация се осигурява от Конфигуриране API. Това е темата на следващия раздел.

3.2. В Config API

В Config API предоставя инструменти за четене на конфигурационни данни от източник на конфигурация .

Helidon SE предоставя реализации за много източници на конфигурация. Изпълнението по подразбиране се осигурява от helidon-config, където източникът на конфигурация е файл application.properties, разположен под пътя на класа:

 io.helidon.config helidon-config 0.10.4 

За да прочетем конфигурационните данни, просто трябва да използваме конструктора по подразбиране, който по подразбиране взема конфигурационните данни от application.properties:

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

Нека създадем файл application.properties под директорията src / main / resource със следното съдържание:

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

За да прочетем стойностите, можем да използваме метода Config.get () , последван от удобно преливане към съответните типове Java:

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();

Всъщност конструкторът по подразбиране зарежда първия намерен файл в този приоритетен ред: application.yaml, application.conf, application.json и application.properties. Последните три формата се нуждаят от допълнително свързана конфигурационна зависимост. Например, за да използваме YAML формата, трябва да добавим свързаната YAML конфигурационна зависимост:

 io.helidon.config helidon-config-yaml 0.10.4 

И след това добавяме application.yml :

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

По същия начин, за да използваме CONF, който е JSON опростен формат, или JSON формати, трябва да добавим зависимостта helidon-config-hocon.

Обърнете внимание, че данните за конфигурацията в тези файлове могат да бъдат заменени от променливи на околната среда и свойства на Java System.

Също така можем да контролираме поведението на конструктора по подразбиране, като деактивираме променливата на околната среда и свойствата на системата или като посочим изрично източника на конфигурацията:

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

В допълнение към четенето на конфигурационни данни от classpath, можем да използваме и две конфигурации на външни източници, тоест конфигурациите на git и etcd. За целта ни трябват зависимостите helidon-config-git и helidon-git-etcd.

Finally, if all of these configuration sources don't satisfy our need, Helidon allows us to provide an implementation for our configuration source. For example, we can provide an implementation that can read the configuration data from a database.

3.3. The Routing API

The Routing API provides the mechanism by which we bind HTTP requests to Java methods. We can accomplish this by using the request method and path as matching criteria or the RequestPredicate object for using more criteria.

So, to configure a route, we can just use the HTTP method as criteria:

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

Or we can combine the HTTP method with the request path:

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

We can also use the RequestPredicate for more control. For example, we can check for an existing header or for the content type:

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();

Until now, we have provided handlers in the functional style. We can also use the Service class which allows writing handlers in a more sophisticated manner.

So, let's first create a model for the object we're working with, the Book class:

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

We can create REST Services for the Book class by implementing the Service.update() method. This allows configuring the subpaths of the same resource:

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); } //... }

We've also configured the Media Type as JSON, so we need the helidon-webserver-json dependency for this purpose:

 io.helidon.webserver helidon-webserver-json 0.10.4 

Finally, we use the register() method of the Routing builder to bind the root path to the resource. In this case, Paths configured by the service are prefixed by the root path:

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

We can now start the server and check the endpoints:

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

3.4. Security

In this section, we're going to secure our resources using the Security module.

Let's start by declaring all the necessary dependencies:

 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 

The helidon-security, helidon-security-provider-http-auth, and helidon-security-integration-webserver dependencies are available from Maven Central.

The security module offers many providers for authentication and authorization. For this example, we'll use the HTTP basic authentication provider as it's fairly simple, but the process for other providers is almost the same.

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();

Or we can use a configuration approach.

In this case, we'll declare all the security configuration in the application.yml file which we load through the Config API:

#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"]

And to load it, we need just to create a Config object and then we invoke the Security.fromConfig() method:

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

Once we have the Security instance, we first need to register it with the WebServer using the WebSecurity.from() method:

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

We can also create a WebSecurity instance directly using the config approach by which we load both the security and the web server configuration:

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

We can now add some handlers for the /user and /admin paths, start the server and try to access them:

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 MicroProfile and also provides a runtime for running MicroProfile based microservices.

As we already have an article about Eclipse MicroProfile, we'll check out that source code and modify it to run on Helidon MP.

After checking out the code, we'll remove all dependencies and plugins and add the Helidon MP dependencies to the POM file:

 io.helidon.microprofile.bundles helidon-microprofile-1.2 0.10.4   org.glassfish.jersey.media jersey-media-json-binding 2.26 

The helidon-microprofile-1.2 and jersey-media-json-binding dependencies are available from Maven Central.

Next, we'll add the beans.xml file under the src/main/resource/META-INF directory with this content:

In the LibraryApplication class, override getClasses() method so that the server won't scan for resources:

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

Finally, create a main method and add this code snippet:

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

And that's it. We'll now be able to invoke all the book resources.

5. Conclusion

В тази статия разгледахме основните компоненти на Helidon, като също така показахме как да настроим Helidon SE и MP. Тъй като Helidon MP е само време за изпълнение на Eclipse MicroProfile, ние можем да стартираме всяка съществуваща микрослужба, базирана на MicroProfile, използвайки го.

Както винаги, кодът на всички примери по-горе може да бъде намерен в GitHub.