Layanan mikro dengan Oracle Helidon

1. Ikhtisar

Helidon adalah framework layanan mikro Java baru yang telah dibuka bersumber terbuka baru-baru ini oleh Oracle. Itu digunakan secara internal dalam proyek Oracle dengan nama J4C (Java untuk Cloud).

Dalam tutorial ini, kita akan membahas konsep utama kerangka kerja dan kemudian kita akan bergerak untuk membangun dan menjalankan layanan mikro berbasis Helidon.

2. Model Pemrograman

Saat ini, kerangka kerja mendukung dua model pemrograman untuk menulis layanan mikro: Helidon SE dan Helidon MP.

Sementara Helidon SE dirancang menjadi microframework yang mendukung model pemrograman reaktif, Helidon MP, di sisi lain, adalah runtime MicroProfile Eclipse yang memungkinkan komunitas EE Jakarta menjalankan layanan mikro secara portabel.

Dalam kedua kasus tersebut, layanan mikro Helidon adalah aplikasi Java SE yang memulai server HTTP nyaring dari metode utama.

3. Helidon SE

Di bagian ini, kita akan menemukan lebih detail komponen utama Helidon SE: WebServer, Config, dan Keamanan.

3.1. Menyiapkan WebServer

Untuk memulai API WebServer , kita perlu menambahkan dependensi Maven yang diperlukan ke file pom.xml :

 io.helidon.webserver helidon-webserver 0.10.4 

Untuk memiliki aplikasi web sederhana, kita dapat menggunakan salah satu metode pembangun berikut: WebServer.create (serverConfig, routing) atau hanya WebServer.create (routing) . Yang terakhir mengambil konfigurasi server default yang memungkinkan server berjalan pada port acak.

Berikut adalah aplikasi Web sederhana yang berjalan pada port yang telah ditentukan sebelumnya. Kami juga telah mendaftarkan penangan sederhana yang akan merespons dengan pesan ucapan untuk permintaan HTTP apa pun dengan jalur '/ salam' dan Metode 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 memulai server dan menunggu untuk melayani permintaan HTTP. Tetapi jika kita menjalankan kode contoh ini dalam metode utama, kita akan mendapatkan kesalahan:

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

The WebServer adalah Sebenarnya SPI, dan kami perlu menyediakan implementasi runtime. Saat ini, Helidon menyediakan implementasi NettyWebServer yang didasarkan pada Netty Core.

Berikut dependensi Maven untuk implementasi ini:

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

Sekarang, kita dapat menjalankan aplikasi utama dan memeriksa apakah itu berfungsi dengan memanggil titik akhir yang dikonfigurasi:

//localhost:9001/greet

Dalam contoh ini, kami mengonfigurasi port dan jalur menggunakan pola builder.

Helidon SE juga memungkinkan penggunaan pola konfigurasi di mana data konfigurasi disediakan oleh Config API. Ini adalah pokok bahasan pada bagian selanjutnya.

3.2. The Config API

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

Helidon SE menyediakan implementasi untuk banyak sumber konfigurasi. Implementasi default disediakan oleh helidon-config di mana sumber konfigurasinya 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 builder default yang secara default mengambil data konfigurasi dari application.properties:

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

Mari buat file application.properties di bawah direktori src / main / resource dengan konten berikut:

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

Untuk membaca nilai-nilai kita dapat menggunakan Config.get () metode diikuti dengan pengecoran nyaman untuk jenis Java yang sesuai:

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

Nyatanya, pembuat default memuat file yang pertama kali ditemukan dalam urutan prioritas ini: application.yaml, application.conf, application.json, dan application.properties. Tiga format terakhir membutuhkan ketergantungan konfigurasi ekstra terkait. Misalnya, untuk menggunakan format YAML, kita perlu menambahkan dependensi konfigurasi YAML:

 io.helidon.config helidon-config-yaml 0.10.4 

Dan kemudian, kami menambahkan application.yml :

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

Demikian pula, untuk menggunakan CONF, yang merupakan format JSON yang disederhanakan, atau format JSON, kita perlu menambahkan ketergantungan helidon-config-hocon.

Perhatikan bahwa data konfigurasi di file ini dapat diganti oleh variabel lingkungan dan properti Sistem Java.

Kami juga dapat mengontrol perilaku pembuat default dengan menonaktifkan variabel Lingkungan dan properti Sistem atau dengan menentukan sumber konfigurasi secara eksplisit:

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 eksternal, yaitu konfigurasi git dan etcd. Untuk ini, kita membutuhkan dependensi 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, kita telah mempelajari komponen utama Helidon, juga menunjukkan cara mengatur Helidon SE dan MP. Karena Helidon MP hanyalah runtime MicroProfile Eclipse, kita dapat menjalankan layanan mikro berbasis MicroProfile yang ada dengan menggunakannya.

Seperti biasa, kode dari semua contoh di atas dapat ditemukan di GitHub.