Memulai dengan layanan microser di Spring Boot

Halo semuanya!

Pada artikel ini, kami akan menunjukkan komponen dasar untuk membuat microservices RESTful menggunakan registri layanan Konsul, Spring Boot untuk semua perancah, injeksi dependensi, Maven untuk perakitan, serta Spring REST dan Java RESTful Jersey / JaxRS API.

Keuntungan utama dari layanan microser:

  • Microservices membantu melonggarkan kode Anda

  • Microservices memungkinkan tim yang berbeda untuk bekerja pada komponen kecil menggunakan teknologi independen, menyediakan penyebaran yang lebih aman dan sering. Spring Boot mendukung berbagai implementasi untuk membuat API REST

  • Penemuan dan panggilan layanan tidak tergantung pada platform layanan

  • Swagger menciptakan dokumentasi API dan antarmuka panggilan yang kuat

Jika Anda belum menggunakan layanan Microsoft, Anda belum berhasil masuk ke fase pengikut awal pada kurva persepsi teknologi, dan mungkin ini saatnya untuk memulai.



Selama dua dekade terakhir, perusahaan telah menjadi sangat fleksibel dalam proses SDLC kami, tetapi aplikasi kami, sebagai suatu peraturan, masih tetap monolitik, dengan toples besar yang mendukung semua berbagai API dan versi di pasar. Tetapi saat ini, ada keinginan untuk lebih Ramping, proses DevOps, dan fungsi menjadi "serverless". Refactoring ke layanan microser dapat mengurangi keterikatan kode dan sumber daya, membuat perakitan lebih kecil, rilis lebih aman, dan API lebih stabil.

Dalam artikel ini, kami akan membuat aplikasi manajemen portofolio pasar saham sederhana yang dapat dihubungi pelanggan untuk mengevaluasi portofolio saham mereka (ticker dan nilai saham). Layanan portofolio mikro akan mengambil portofolio klien, mengirimkannya ke layanan harga mikro untuk menerapkan harga terbaru, dan kemudian mengembalikan portofolio yang sepenuhnya dievaluasi dan disubotisasi, menunjukkan semua ini melalui panggilan sisa.



Sebelum kita mulai membuat microservices, mari siapkan lingkungan kita dengan mengatur Konsul.

Unduh Konsul


Kami akan menggunakan Konsul Hashicorp untuk menemukan layanan, jadi kunjungi www.consul.io/downloads.html dan unduh Konsul untuk Windows, Linux, Mac, dll. Ini akan memberi Anda file yang dapat dieksekusi yang perlu Anda tambahkan ke jalur Anda.

Luncurkan Konsul


Pada prompt perintah, meluncurkan Konsul dalam mode dev:

consul agent -dev 

Untuk memverifikasi bahwa itu sedang berjalan, buka browser Anda dan akses antarmuka konsul http: // localhost: 8500 . Jika semuanya berjalan dengan baik, konsul harus melaporkan bahwa ia masih hidup dan sehat. Dengan mengklik pada layanan konsul (di sebelah kiri), Anda akan menerima informasi tambahan (di sebelah kanan).



Jika saat ini ada masalah, pastikan Anda menambahkan Konsul ke jalur eksekusi, dan port 8500 dan 8600 tersedia.

Bangun Aplikasi SpringBoot


Kami akan menggunakan Spring Initializr , yang terintegrasi ke dalam sebagian besar IDE, untuk merancah aplikasi SpringBoot kami. Tangkapan layar di bawah ini menggunakan IntelliJ IDEA.

Pilih "File / Proyek Baru" untuk membuka templat proyek baru, dan kemudian "Spring Initializr".



Secara umum, Anda dapat mengatur perancah tanpa IDE dengan mengisi formulir online melalui halaman web SpringBoot Initializr start.spring.io , yang akan membuat file zip untuk proyek kosong Anda, siap untuk diunduh.

Klik "Next" dan isi metadata proyek. Gunakan konfigurasi berikut:



Klik "Next" untuk memilih dependensi, dan masukkan "Jersey" dan "Consul Discovery" dalam pencarian dependensi. Tambahkan dependensi ini:



Klik "Next" untuk menunjukkan nama proyek dan lokasinya. Simpan nama "portofolio" default dan tentukan lokasi proyek yang disukai, lalu klik "selesai" untuk membuat dan membuka proyek:



Kita dapat menggunakan properti application.prop yang dihasilkan, tetapi SpringBoot juga mengenali format YAML, yang sedikit lebih mudah untuk divisualisasikan, jadi mari kita ganti namanya menjadi application.yml.

Kami menyebut microservice "layanan portofolio". Kita dapat menentukan port atau menggunakan port 0 sehingga aplikasi menggunakan port yang tersedia. Dalam kasus kami, kami akan menggunakan 57116. Jika Anda meng-host layanan ini sebagai wadah Docker, Anda dapat memetakannya ke port yang Anda pilih. Beri nama aplikasi dan tentukan port kami dengan menambahkan yang berikut ke application.yml kami:

 spring: application: name: portfolio-service server: port: 57116 

Untuk membuat layanan kami tersedia, tambahkan anotasi ke kelas aplikasi SpringBoot kami. Buka aplikasi PortfolioApplication dan tambahkan @EnableDiscoveryClient di atas deklarasi kelas.

Konfirmasikan impor. Kelas akan terlihat seperti ini:

 package com.restms.demo.portfolio; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; . . . @SpringBootApplication @EnableDiscoveryClient public class PortfolioApplication { public static void main(String[] args) { SpringApplication.run(PortfolioApplication.class, args); } } 

(Untuk menunjukkan bagaimana layanan microsoft dapat terdiri dari platform independen, kami akan menggunakan Jersey untuk layanan ini dan Spring REST untuk selanjutnya).
Untuk mengkonfigurasi layanan web RESTful di Jersey, kita perlu menentukan kelas konfigurasi ResourceConfig. Tambahkan kelas JerseyConfig (untuk demonstrasi, kami akan menyimpannya dalam paket yang sama dengan kelas aplikasi kami). Seharusnya terlihat seperti ini, ditambah paket yang benar dan impor:

 @Configuration @ApplicationPath("portfolios") public class JerseyConfig extends ResourceConfig { public JerseyConfig() { register(PortfolioImpl.class); } } 

Perhatikan bahwa ia mewarisi dari ResourceConfig untuk menetapkannya sebagai kelas konfigurasi Jersey. Atribut @ApplicationPath ("portfolios") mendefinisikan konteks panggilan, yang berarti bahwa panggilan harus dimulai dengan elemen path "portfolioios". (Jika Anda menghilangkannya, konteks default adalah "/").

Kelas PortfolioImpl akan melayani dua permintaan: portofolio / pelanggan / {pelanggan-id} mengembalikan semua portofolio dan portofolio / pelanggan / {pelanggan-id} / portofolio / {portfolio-id} mengembalikan satu portofolio. Portofolio terdiri dari seperangkat ticker dan jumlah saham yang dimiliki oleh ticker itu. (Untuk demonstrasi, ada tiga klien dengan pengidentifikasi 0, 1 dan 2, yang masing-masing memiliki tiga portofolio dengan pengidentifikasi 0, 1 dan 2).

IDE Anda akan meminta Anda untuk membuat PortfolioImpl; lakukan sekarang. Untuk menunjukkan, tambahkan ke paket yang sama. Masukkan kode di bawah ini dan konfirmasikan semua impor:

 @Component @Path("/") public class PortfolioImpl implements InitializingBean { private Object[][][][] clientPortfolios; @GET @Path("customer/{customer-id}") @Produces(MediaType.APPLICATION_JSON) // a portfolio consists of an array of arrays, each containing an array of // stock ticker and associated shares public Object[][][] getPortfolios(@PathParam("customer-id") int customerId) { return clientPortfolios[customerId]; } @GET @Path("customer/{customer-id}/portfolio/{portfolio-id}") @Produces(MediaType.APPLICATION_JSON) public Object[][] getPortfolio(@PathParam("customer-id") int customerId, @PathParam("portfolio-id") int portfolioId) { return getPortfolios(customerId)[portfolioId]; } @Override public void afterPropertiesSet() throws Exception { Object[][][][] clientPortfolios = { { // 3 customers, 3 portfolios each {new Object[]{"JPM", 10201}, new Object[]{"GE", 20400}, new Object[]{"UTX", 38892}}, {new Object[]{"KO", 12449}, new Object[]{"JPM", 23454}, new Object[]{"MRK", 45344}}, {new Object[]{"WMT", 39583}, new Object[]{"DIS", 95867}, new Object[]{"TRV", 384756}}, }, { {new Object[]{"GE", 38475}, new Object[]{"MCD", 12395}, new Object[]{"IBM", 91234}}, {new Object[]{"VZ", 22342}, new Object[]{"AXP", 385432}, new Object[]{"UTX", 23432}}, {new Object[]{"IBM", 18343}, new Object[]{"DIS", 45673}, new Object[]{"AAPL", 23456}}, }, { {new Object[]{"AXP", 34543}, new Object[]{"TRV", 55322}, new Object[]{"NKE", 45642}}, {new Object[]{"CVX", 44332}, new Object[]{"JPM", 12453}, new Object[]{"JNJ", 45433}}, {new Object[]{"MRK", 32346}, new Object[]{"UTX", 46532}, new Object[]{"TRV", 45663}}, } }; this.clientPortfolios = clientPortfolios; } } 

Anotasi Komponen menunjuk ini sebagai kelas komponen Pegas dan menyediakannya sebagai titik akhir. Anotasi jalur tentang deklarasi kelas menyatakan bahwa kelas diakses melalui elemen path โ€œ/โ€, dan dua panggilan api yang didukung tersedia melalui portofolio / pelanggan / {customer-id} dan portofolio / pelanggan / {customer-id} / portfolio / {portfolio- id}, seperti yang kita lihat dari penjelasan metode. Perhatikan bahwa path ("/") adalah default, tetapi kami membiarkannya untuk referensi. Metode ditetapkan sebagai HTTP DAPATKAN melalui @GETannotation. Metode kami dirancang untuk mengembalikan array dan beranotasi untuk mengembalikan Json, sehingga mengembalikan array Json. Perhatikan bagaimana anotasi Path Param digunakan dalam tanda tangan metode untuk mengekstrak parameter yang ditampilkan dari kueri yang ditampilkan.

(Untuk demo kami, kami mengembalikan nilai hardcoded. Tentu saja, dalam praktiknya, implementasi akan meminta basis data atau layanan lain atau sumber data alih-alih hardcode.)
Sekarang buat proyek dan jalankan. Jika Anda menggunakan IntelliJ, itu akan membuat executable default, jadi cukup klik panah "run" hijau. Anda juga bisa menggunakan

 mvn spring-boot:run 

Atau Anda dapat melakukan instalasi maven dan menjalankan aplikasi menggunakan java -jar, menunjuk ke jar yang dihasilkan di direktori target:

 java -jar target\portfolio-0.0.1-SNAPSHOT.jar 

Sekarang kita akan melihat layanan ini di Konsul, jadi mari kembali ke browser kami, unduh http: // localhost: 8500 / ui / # / dc1 / services (atau perbarui jika Anda sudah ada di sana).



Hmm, kami melihat layanan kami di sana, tetapi ditampilkan sebagai gagal. Ini karena Konsul mengharapkan sinyal detak jantung "sehat" dari layanan kami.
Untuk menghasilkan sinyal detak jantung, kita dapat menambahkan ketergantungan pada layanan Spring Actuator ke pom aplikasi kita.

 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> 

Sementara kita berada di pom, perhatikan bahwa ada konflik versi dengan Jersey antara starter Konsul dan starter Jersey. Untuk memperlancar ini, tentukan starter Jersey sebagai kecanduan pertama.

Pom Anda sekarang harus berisi dependensi berikut:

 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jersey</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> 

Dengan memulai kembali Konsul, layanan Portofolio menampilkan yang bahagia:



Sekarang dalam layanan portofolio ada dua node transmisi: salah satunya adalah implementasi layanan portofolio kami, dan yang lainnya adalah detak jantung.

Mari kita periksa port yang telah ditetapkan. Anda dapat melihat itu di output aplikasi:

 INFO 19792 --- [ main] sbcetTomcatEmbeddedServletContainer : Tomcat started on port(s): 57116 (http) 

Anda juga dapat melihat port langsung di antarmuka pengguna Konsul. Klik "layanan pelanggan", lalu pilih tautan "Layanan 'layanan pelanggan' ', yang menampilkan port layanan, dalam hal ini 57116.



Minta http: // localhost: 57116 / portofolio / pelanggan / 1 / portofolio / 2 dan Anda akan melihat json array [["IBM", 18343], ["DIS", 45673], ["AAPL", 23456]]

Layanan microser pertama kami terbuka untuk bisnis!

Layanan penetapan harga


Selanjutnya, kami akan membuat layanan penetapan harga kami, kali ini menggunakan Spring RestController alih-alih Jersey.

Layanan penetapan harga akan menerima pengidentifikasi klien dan pengidentifikasi portofolio sebagai parameter dan akan menggunakan RestTemplate untuk meminta layanan portofolio, menerima ticker dan stok, dan mengembalikan harga saat ini. (Saya tidak perlu memberi tahu Anda bahwa nilai-nilai ini adalah berita palsu, jadi jangan menggunakannya untuk membuat keputusan perdagangan!)

Buat proyek baru menggunakan informasi berikut:



Kali ini, pilih dependensi Web, Consul Discovery, dan Actuator:



Biarkan nama default proyek โ€œpenetapan hargaโ€ dan buat proyek di direktori pilihan Anda.

Kali ini kita akan menggunakan application.properties alih-alih application.yml.
Tetapkan nama dan porta di application.properties sebagai:

 spring.application.name=pricing server.port=57216 

Annotate PricingApplication dengan @EnableDiscoveryClient. Kelas akan terlihat seperti ini, ditambah paket dan impor.

 @SpringBootApplication @EnableDiscoveryClient public class PricingApplication { public static void main(String[] args) { SpringApplication.run(PricingApplication.class, args); } } 

Kemudian kita akan membuat kelas PricingEndpoint. Di sini saya akan memberikan contoh yang lebih rinci, karena menunjukkan beberapa fungsi penting, termasuk penemuan layanan (mencari layanan portofolio) dan menggunakan RestTemplate untuk permintaan:

 @RestController @RequestMapping("/pricing") public class PricingEndpoint implements InitializingBean { @Autowired DiscoveryClient client; Map<String, Double> pricingMap = new HashMap<>(); RestTemplate restTemplate = new RestTemplate(); @GetMapping("/customer/{customer-id}/portfolio/{portfolio-id}") public List<String> getPricedPortfolio( @PathVariable("customer-id") Integer customerId, @PathVariable("portfolio-id") Integer portfolioId) { List<ServiceInstance> instances = client.getInstances("portfolio-service"); ServiceInstance instance = instances.stream() .findFirst() .orElseThrow(() -> new RuntimeException("not found")); String url = String.format("%s/portfolios/customer/%d/portfolio/%d", instance.getUri(), customerId, portfolioId); // query for the portfolios, returned as an array of List // of size 2, containing a ticker and a position (# of shares) Object[] portfolio = restTemplate.getForObject(url, Object[].class); // Look up the share prices, and return a list of Strings, formatted as // ticker, shares, price, total List<String> collect = Arrays.stream(portfolio).map(position -> { String ticker = ((List<String>) position).get(0); int shares = ((List<Integer>) position).get(1); double price = getPrice(ticker); double total = shares * price; return String.format("%s %d %f %f", ticker, shares, price, total); }).collect(Collectors.toList()); return collect; } private double getPrice(String ticker) { return pricingMap.get(ticker); } @Override public void afterPropertiesSet() throws Exception { pricingMap.put("MMM",201.81); pricingMap.put("AXP",85.11); pricingMap.put("AAPL",161.04); pricingMap.put("BA",236.32); pricingMap.put("CAT",118.02); pricingMap.put("CVX",111.31); pricingMap.put("CSCO",31.7); pricingMap.put("KO",46.00); pricingMap.put("DIS",101.92); pricingMap.put("XOM",78.7); pricingMap.put("GE",24.9); pricingMap.put("GS",217.62); pricingMap.put("HD",155.82); pricingMap.put("IBM",144.29); pricingMap.put("INTC",35.66); pricingMap.put("JNJ",130.8); pricingMap.put("JPM",89.75); pricingMap.put("MCD",159.81); pricingMap.put("MRK",63.89); pricingMap.put("MSFT",73.65); pricingMap.put("NKE",52.78); pricingMap.put("PFE",33.92); pricingMap.put("PG",92.79); pricingMap.put("TRV",117.00); pricingMap.put("UTX",110.12); pricingMap.put("UNH",198.00); pricingMap.put("VZ",47.05); pricingMap.put("V",103.34); pricingMap.put("WMT", 80.05); } } 

Untuk menemukan layanan portofolio, kita perlu memiliki akses ke DiscoveryClient. Sangat mudah untuk mendapatkan dengan anotasi @Autowired Spring.

 @Autowired DiscoveryClient client; 

Contoh DiscoveryClient ini kemudian digunakan untuk mencari layanan dalam panggilan:

 List<ServiceInstance> instances = client.getInstances("portfolio-service"); ServiceInstance instance = instances.stream().findFirst().orElseThrow(() -> new RuntimeException("not found")); 

Setelah layanan ditemukan, kami dapat menggunakannya untuk memenuhi permintaan kami, yang kami susun sesuai dengan panggilan api yang dibuat dalam layanan portofolio kami.

 String url = String.format("%s/portfolios/customer/%d/portfolio/%d", instance.getUri(), customerId, portfolioId); 

Terakhir, kami menggunakan RestTemplate untuk menjalankan permintaan GET kami.

 Object[] portfolio = restTemplate.getForObject(url, Object[].class); 

Perhatikan bahwa untuk pengendali Rest (dan juga untuk Spring MVC Request Controller) variabel path diambil menggunakan anotasi Path Variable, tidak seperti Jersey, yang, seperti yang kita lihat, menggunakan Path Param.

Ini menyimpulkan harga kami dengan Spring RestController.

Dokumentasi


Kami memecahkan semua masalah ini untuk menciptakan layanan microser kami, tetapi mereka tidak akan membawa manfaat yang cukup jika kami tidak memberikan pengetahuan dunia tentang cara menggunakannya.

Untuk melakukan ini, kami menggunakan alat Swagger yang nyaman dan mudah digunakan, yang tidak hanya mendokumentasikan panggilan API kami, tetapi juga menyediakan klien web yang nyaman untuk memanggil mereka.

Pertama, mari kita tentukan Swagger di pom kami:

 <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.7.0</version> </dependency> 

Maka kita perlu memberi tahu Swagger tentang kelas mana yang ingin kita dokumentasikan. Mari kita perkenalkan kelas SwaggerConfig baru yang berisi spesifikasi Swagger.

 @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.regex("/pricing.*")) .build(); } } 

Mari kita lihat apa yang dilakukan kelas ini. Pertama, kami menetapkan ini sebagai konfigurasi Swagger dengan penjelasan @ EnableSwagger2.

Lalu kami membuat komponen map yang memberi tahu Swagger API mana yang harus ditampilkan. Dalam contoh di atas, kami memberi tahu Swagger untuk menunjukkan jalan apa pun yang dimulai dengan "/ harga". Alternatifnya adalah dengan menentukan kelas untuk dokumentasi, dan bukan untuk jalur:

 .apis(RequestHandlerSelectors.basePackage("com.restms.demo")) .paths(PathSelectors.any()) 

Mulai ulang microservice harga dan hubungi http: // localhost: 57216 / swagger-ui.html dari browser



Klik Operasi Daftar untuk melihat operasi layanan secara rinci.
Klik Perluas Operasi untuk membuat permintaan berdasarkan formulir. Tetapkan beberapa parameter, klik "Cobalah!" dan tunggu jawabannya:



Anda dapat menambahkan lebih banyak warna dengan menambahkan penjelasan Swagger ke metode Anda.
Misalnya, hiasi metode PricingImpl.getPricedPortfolio yang ada menggunakan anotasi @ApiOperation, seperti yang ditunjukkan di bawah ini:

 @ApiOperation(value = "Retrieves a fully priced portfolio", notes = "Retrieves fully priced portfolio given customer id and portfolio id") @GetMapping("/customer/{customer-id}/portfolio/{portfolio-id}") public List<String> getPricedPortfolio(@PathVariable("customer-id") Integer customerId, @PathVariable("portfolio-id") Integer portfolioId) 

Muat ulang dan perbarui swagger-ui untuk melihat dokumentasi baru yang diperbarui:



Dan itu tidak semua yang dapat Anda lakukan dengan Swagger, jadi lihat dokumentasi.

Yuri Dvorzhetsky , dosen mata kuliah kami "Pengembang Kerangka Kerja Musim Semi", akan memberi tahu Anda lebih banyak tentang pekerjaan Boot Musim Semi:


Artikel asli

Source: https://habr.com/ru/post/id413567/


All Articles