Perkhidmatan Mikro dengan Oracle Helidon

1. Gambaran keseluruhan

Helidon adalah kerangka perkhidmatan mikro Java baru yang dibuka sumbernya baru-baru ini oleh Oracle. Itu digunakan secara dalaman dalam projek Oracle dengan nama J4C (Java for Cloud).

Dalam tutorial ini, kita akan merangkumi konsep utama kerangka dan kemudian kita bergerak untuk membina dan menjalankan perkhidmatan mikro berdasarkan Helidon.

2. Model Pengaturcaraan

Pada masa ini, kerangka ini menyokong dua model pengaturcaraan untuk menulis perkhidmatan mikro: Helidon SE dan Helidon MP.

Walaupun Helidon SE dirancang untuk menjadi kerja mikro yang mendukung model pengaturcaraan reaktif, Helidon MP, di sisi lain, adalah runtime Eclipse MicroProfile yang memungkinkan masyarakat Jakarta EE menjalankan perkhidmatan mikro dengan cara mudah alih.

Dalam kedua kes tersebut, Helidon microservice adalah aplikasi Java SE yang memulakan pelayan HTTP kecil dari kaedah utama.

3. Helidon SE

Di bahagian ini, kita akan menemui dengan lebih terperinci komponen utama Helidon SE: WebServer, Config, dan Security.

3.1. Menyiapkan Pelayan Web

Untuk memulakan dengan WebServer API , kita perlu menambahkan kebergantungan Maven yang diperlukan ke fail pom.xml :

 io.helidon.webserver helidon-webserver 0.10.4 

Untuk memiliki aplikasi web yang sederhana, kita dapat menggunakan salah satu kaedah pembangun berikut: WebServer.create (serverConfig, routing) atau hanya WebServer.create (routing) . Yang terakhir mengambil konfigurasi pelayan lalai yang membolehkan pelayan berjalan pada port rawak.

Berikut adalah aplikasi Web ringkas yang berjalan di port yang telah ditentukan. Kami juga telah mendaftarkan pengendali mudah yang akan bertindak balas dengan mesej ucapan untuk sebarang permintaan HTTP dengan jalan '/ salam' dan Kaedah 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()) ); }

Baris terakhir adalah untuk memulakan pelayan dan menunggu untuk melayani permintaan HTTP. Tetapi jika kita menjalankan contoh kod ini dalam kaedah utama, kita akan mendapat ralat:

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

The pelayan Web adalah Sebenarnya SPI, dan kita perlu menyediakan pelaksanaan runtime. Pada masa ini, Helidon menyediakan pelaksanaan NettyWebServer yang berdasarkan pada Netty Core.

Inilah ketergantungan Maven untuk pelaksanaan ini:

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

Sekarang, kita dapat menjalankan aplikasi utama dan memastikan ia berfungsi dengan menggunakan titik akhir yang dikonfigurasi:

//localhost:9001/greet

Dalam contoh ini, kami mengkonfigurasi port dan jalan menggunakan corak pembangun.

Helidon SE juga membolehkan menggunakan corak konfigurasi di mana data konfigurasi disediakan oleh Config API. Ini subjek bahagian seterusnya.

3.2. The Config API

The Config API menyediakan alat untuk membaca data konfigurasi dari sumber konfigurasi .

Helidon SE menyediakan pelaksanaan untuk banyak sumber konfigurasi. Pelaksanaan lalai disediakan oleh helidon-config di mana sumber konfigurasi adalah file application.properties yang terletak di bawah classpath:

 io.helidon.config helidon-config 0.10.4 

Untuk membaca data konfigurasi, kita hanya perlu menggunakan pembangun lalai yang secara lalai mengambil data konfigurasi dari application.properties:

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

Mari buat fail application.properties di bawah direktori src / main / sumber dengan kandungan berikut:

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

Untuk membaca nilai yang kita boleh menggunakan Config.get () kaedah diikuti oleh pemutus yang mudah untuk jenis Java koresponden:

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

Sebenarnya, pembangun lalai memuatkan fail pertama yang dijumpai dalam urutan keutamaan ini: application.yaml, application.conf, application.json, dan application.properties. Tiga format terakhir memerlukan pergantungan konfigurasi yang berkaitan. Sebagai contoh, untuk menggunakan format YAML, kita perlu menambahkan kebergantungan konfigurasi YAML yang berkaitan:

 io.helidon.config helidon-config-yaml 0.10.4 

Dan kemudian, kami menambah aplikasi.yml :

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

Begitu juga, untuk menggunakan CONF, yang merupakan format dipermudahkan JSON, atau format JSON, kita perlu menambahkan kebergantungan helidon-config-hocon.

Perhatikan bahawa data konfigurasi dalam fail ini dapat diganti oleh pemboleh ubah persekitaran dan sifat Sistem Java.

Kami juga dapat mengawal tingkah laku pembangun lalai dengan menonaktifkan pemboleh ubah persekitaran dan sifat Sistem atau dengan menyatakan secara jelas sumber konfigurasi:

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

Selain membaca data konfigurasi dari classpath, kita juga dapat menggunakan dua konfigurasi sumber luaran, iaitu konfigurasi git dan etcd. Untuk ini, kita memerlukan pergantungan helidon-config-git dan 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

Dalam artikel ini, kami telah meneroka komponen utama Helidon, juga menunjukkan cara menyediakan Helidon SE dan MP. Oleh kerana Helidon MP hanyalah runtime Eclipse MicroProfile, kita dapat menjalankan microservice berasaskan MicroProfile yang ada menggunakannya.

Seperti biasa, kod semua contoh di atas boleh didapati di GitHub.