Arsitektur microservice pada tumpukan teknologi Java modern

Kami memiliki JDK 11, Kotlin, Spring 5 dan Spring Boot 2, Gradle 5 dengan DSL siap-siap Kotlin, JUnit 5, dan selusin pustaka Spring Cloud stack untuk penemuan Layanan, membuat API gateway, menyeimbangkan klien, dan menerapkan Pemutus Sirkuit menulis klien HTTP deklaratif, tracing terdistribusi dan semua itu. Bukan berarti semua ini diperlukan untuk membuat arsitektur layanan mikro - hanya untuk bersenang-senang ...

Entri


Pada artikel ini, Anda akan melihat contoh arsitektur layanan mikro menggunakan teknologi yang relevan di dunia Java, yang utamanya diberikan di bawah ini (versi yang ditunjukkan digunakan dalam proyek pada saat publikasi):
Jenis teknologiJudulVersi
PlatformJdk11.0.1
Bahasa pemrogramanKotlin1.3.10
Kerangka kerja aplikasiKerangka pegas5.0.9
Boot musim semi2.0.5
Membangun sistemGradle5.0
Gradle Kotlin DSL1.0.4
Kerangka Pengujian UnitJunit5.1.1
Awan musim semi
Titik Akses Tunggal (gateway API)Spring cloud gatewayTermasuk dalam rilis kereta proyek Finchley SR2 Spring Cloud
Konfigurasi terpusatKonfigurasi cloud spring
Permintaan Pelacakan (Pelacakan Terdistribusi)Sulap awan musim semi
Klien HTTP deklaratifSpring Cloud OpenFeign
Penemuan layananSpring Cloud Netflix Eureka
Pemutus sirkuitAwan Musim Semi Netflix Hystrix
Penyeimbangan beban sisi klienPita Awan Netflix Musim Semi

Proyek ini terdiri dari 5 layanan mikro: 3 infrastruktur (Server konfigurasi, Server layanan pencarian, gateway UI) dan contoh front-end (Item UI) dan back-end (Layanan item):


Semuanya akan dipertimbangkan secara berurutan di bawah ini. Dalam proyek "pertempuran", jelas, akan ada lebih banyak layanan microser yang mengimplementasikan fungsionalitas bisnis apa pun. Menambahkannya ke arsitektur serupa dilakukan secara teknis dengan cara yang sama seperti layanan Item UI dan Items.

Penafian


Artikel ini tidak mempertimbangkan instrumen untuk kontainerisasi dan orkestrasi, karena saat ini mereka tidak digunakan dalam proyek.

Server konfigurasi


Spring Cloud Config digunakan untuk membuat repositori terpusat dari konfigurasi aplikasi. Konfigurasi dapat dibaca dari berbagai sumber, misalnya, repositori git yang terpisah; dalam proyek ini, untuk kesederhanaan dan kejelasan, mereka ada dalam sumber daya aplikasi:


Dalam hal ini, konfigurasi server Config ( application.yml ) itu sendiri terlihat seperti ini:

 spring: profiles: active: native cloud: config: server: native: search-locations: classpath:/config server: port: 8888 

Menggunakan port 8888 memungkinkan klien server Config untuk tidak secara eksplisit menentukan port-nya di bootstrap.yml mereka. Saat startup, mereka mengunggah konfigurasi mereka dengan mengeksekusi permintaan GET ke server HTTP API Config.

Kode program untuk layanan Microsoft ini hanya terdiri dari satu file, yang berisi deklarasi kelas aplikasi dan metode utama, yang, tidak seperti kode Java yang setara, adalah fungsi tingkat atas:

 @SpringBootApplication @EnableConfigServer class ConfigServerApplication fun main(args: Array<String>) { runApplication<ConfigServerApplication>(*args) } 

Kelas aplikasi dan metode utama dalam layanan microser lainnya memiliki tampilan yang serupa.

Server penemuan layanan


Penemuan layanan adalah pola arsitektur layanan mikro yang memungkinkan Anda untuk menyederhanakan interaksi antara aplikasi dalam menghadapi kemungkinan perubahan jumlah instance dan lokasi jaringan mereka. Komponen utama dalam pendekatan ini adalah registri Layanan - basis data layanan mikro, instansnya, dan lokasi jaringan (detail lebih lanjut di sini ).

Dalam proyek ini, penemuan Layanan diimplementasikan berdasarkan Netflix Eureka, yang merupakan penemuan layanan sisi-Klien : Server Eureka melakukan fungsi registri Layanan, dan klien Eureka, sebelum mengeksekusi permintaan ke layanan mikro apa pun, hubungi server Eureka untuk daftar contoh aplikasi yang disebut dan secara mandiri melakukan penyeimbangan memuat (menggunakan Pita Netflix). Netflix Eureka, seperti beberapa komponen tumpukan OSS Netflix lainnya (seperti Hystrix dan Ribbon) terintegrasi dengan aplikasi Spring Boot menggunakan Spring Cloud Netflix .

Dalam konfigurasi server penemuan layanan yang terletak di sumber dayanya ( bootstrap.yml ), hanya nama aplikasi dan parameter yang menunjukkan bahwa awal dari layanan microser akan terganggu jika tidak mungkin untuk terhubung ke server konfigurasi ditunjukkan:

 spring: application: name: eureka-server cloud: config: fail-fast: true 

Konfigurasi aplikasi lainnya terletak di file eureka-server.yml di sumber daya server Config:

 server: port: 8761 eureka: client: register-with-eureka: true fetch-registry: false 

Server Eureka menggunakan port 8761, yang memungkinkan semua klien Eureka untuk tidak menentukannya menggunakan nilai default. Nilai register-with-eureka (ditunjukkan untuk kejelasan, register-with-eureka juga digunakan secara default) berarti bahwa aplikasi itu sendiri, seperti layanan microser lainnya, akan terdaftar di server Eureka. Parameter fetch-registry menentukan apakah klien Eureka akan menerima data dari registri Layanan.

Daftar aplikasi terdaftar dan informasi lainnya tersedia di http://localhost:8761/ :


Alternatif untuk mengimplementasikan penemuan Layanan adalah Konsul, Zookeeper, dan lainnya.

Layanan barang


Aplikasi ini adalah contoh back-end dengan REST API yang diimplementasikan menggunakan kerangka WebFlux yang muncul di Spring 5 (dokumentasi ada di sini ), atau lebih tepatnya Kotlin DSL untuk itu:

 @Bean fun itemsRouter(handler: ItemHandler) = router { path("/items").nest { GET("/", handler::getAll) POST("/", handler::add) GET("/{id}", handler::getOne) PUT("/{id}", handler::update) } } 

Memproses permintaan HTTP yang diterima didelegasikan ke ItemHandler kelas ItemHandler . Misalnya, metode untuk mendapatkan daftar objek dari beberapa entitas terlihat seperti ini:

 fun getAll(request: ServerRequest) = ServerResponse.ok() .contentType(APPLICATION_JSON_UTF8) .body(fromObject(itemRepository.findAll())) 

Aplikasi menjadi klien server Eureka, mis., Itu mendaftar dan menerima data dari registri Layanan, karena adanya spring-cloud-starter-netflix-eureka-client . Setelah pendaftaran, aplikasi mengirimkan hartbits ke server Eureka dengan frekuensi tertentu, dan jika untuk periode waktu tertentu persentase hartbits yang diterima oleh server Eureka relatif terhadap nilai maksimum yang mungkin berada di bawah ambang tertentu, aplikasi akan dihapus dari registri Layanan.

Pertimbangkan salah satu cara untuk mengirim metadata tambahan ke server Eureka:

 @PostConstruct private fun addMetadata() = aim.registerAppMetadata(mapOf("description" to "Some description")) 

Pastikan bahwa data ini diterima oleh server Eureka dengan mengunjungi http://localhost:8761/eureka/apps/items-service melalui Postman:



Item UI


Layanan microser ini, selain menunjukkan interaksi dengan gateway UI (akan ditampilkan di bagian berikutnya), melakukan fungsi front-end untuk layanan Item, yang dapat berinteraksi dengan REST API dengan beberapa cara:

  1. Klien ke REST API ditulis menggunakan OpenFeign:

     @FeignClient("items-service", fallbackFactory = ItemsServiceFeignClient.ItemsServiceFeignClientFallbackFactory::class) interface ItemsServiceFeignClient { @GetMapping("/items/{id}") fun getItem(@PathVariable("id") id: Long): String @GetMapping("/not-existing-path") fun testHystrixFallback(): String @Component class ItemsServiceFeignClientFallbackFactory : FallbackFactory<ItemsServiceFeignClient> { private val log = LoggerFactory.getLogger(this::class.java) override fun create(cause: Throwable) = object : ItemsServiceFeignClient { override fun getItem(id: Long): String { log.error("Cannot get item with id=$id") throw ItemsUiException(cause) } override fun testHystrixFallback(): String { log.error("This is expected error") return "{\"error\" : \"Some error\"}" } } } } 
  2. RestTemplate Bean
    Bin dibuat di java config:

     @Bean @LoadBalanced fun restTemplate() = RestTemplate() 

    Dan digunakan dengan cara ini:

     fun requestWithRestTemplate(id: Long): String = restTemplate.getForEntity("http://items-service/items/$id", String::class.java).body ?: "No result" 
  3. WebClient kelas WebClient (metode khusus untuk kerangka WebFlux)
    Bin dibuat di java config:

     @Bean fun webClient(loadBalancerClient: LoadBalancerClient) = WebClient.builder() .filter(LoadBalancerExchangeFilterFunction(loadBalancerClient)) .build() 

    Dan digunakan dengan cara ini:

     fun requestWithWebClient(id: Long): Mono<String> = webClient.get().uri("http://items-service/items/$id").retrieve().bodyToMono(String::class.java) 

Fakta bahwa ketiga metode mengembalikan hasil yang sama dapat diverifikasi dengan membuka http://localhost:8081/example :


Saya lebih suka opsi menggunakan OpenFeign, karena memungkinkan untuk mengembangkan kontrak untuk interaksi dengan microservice yang disebut, implementasi yang dilakukan oleh Spring. Objek yang mengimplementasikan kontrak ini disuntikkan dan digunakan seperti kacang biasa:

 itemsServiceFeignClient.getItem(1) 

Jika permintaan gagal karena beberapa alasan, metode kelas yang sesuai yang mengimplementasikan antarmuka FallbackFactory akan dipanggil, di mana Anda perlu memproses kesalahan dan mengembalikan respons default (atau melemparkan pengecualian lebih lanjut). Jika sejumlah panggilan berturut-turut gagal, Fuse akan membuka sirkuit (lebih lanjut tentang Pemutus sirkuit di sini dan di sini ), memberikan waktu untuk memulihkan microservice yang jatuh.

Untuk menggunakan klien Feign, Anda perlu membuat anotasi @EnableFeignClients aplikasi @EnableFeignClients :

 @SpringBootApplication @EnableFeignClients(clients = [ItemsServiceFeignClient::class]) class ItemsUiApplication 

Agar fallback Hystrix berfungsi di klien Feign, Anda perlu menambahkan yang berikut ke konfigurasi aplikasi:

 feign: hystrix: enabled: true 

Untuk menguji operasi fallback Hystrix di klien Feign, cukup buka http://localhost:8081/hystrix-fallback . Klien Feign akan mencoba mengeksekusi permintaan pada jalur yang tidak ada dalam layanan Item, yang akan mengarah pada kembalinya respons:

 {"error" : "Some error"} 

Gateway UI


Pola gateway API memungkinkan Anda membuat titik masuk tunggal untuk API yang disediakan oleh layanan microser lainnya (detail lebih lanjut di sini ). Aplikasi yang mengimplementasikan pola ini melakukan perutean (perutean) permintaan ke layanan mikro, dan juga dapat melakukan fungsi tambahan, misalnya, otentikasi.

Dalam proyek ini, untuk kejelasan yang lebih besar, gateway UI diimplementasikan, yaitu, titik masuk tunggal untuk UI yang berbeda; jelas, API gateway diimplementasikan dengan cara yang sama. Layanan mikro diimplementasikan berdasarkan kerangka kerja Spring Cloud Gateway. Alternatifnya adalah Netflix Zuul, bagian dari Netflix OSS dan terintegrasi dengan Spring Boot menggunakan Spring Cloud Netflix.
Gateway UI berjalan pada port 443 menggunakan sertifikat SSL yang dihasilkan (terletak di proyek). SSL dan HTTPS dikonfigurasikan sebagai berikut:

 server: port: 443 ssl: key-store: classpath:keystore.p12 key-store-password: qwerty key-alias: test_key key-store-type: PKCS12 

Login dan kata sandi pengguna disimpan dalam implementasi berbasis peta dari antarmuka ReactiveUserDetailsService khusus WebFlux:

 @Bean fun reactiveUserDetailsService(): ReactiveUserDetailsService { val user = User.withDefaultPasswordEncoder() .username("john_doe").password("qwerty").roles("USER") .build() val admin = User.withDefaultPasswordEncoder() .username("admin").password("admin").roles("ADMIN") .build() return MapReactiveUserDetailsService(user, admin) } 

Pengaturan keamanan dikonfigurasi sebagai berikut:

 @Bean fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain = http .formLogin().loginPage("/login") .and() .authorizeExchange() .pathMatchers("/login").permitAll() .pathMatchers("/static/**").permitAll() .pathMatchers("/favicon.ico").permitAll() .pathMatchers("/webjars/**").permitAll() .pathMatchers("/actuator/**").permitAll() .anyExchange().authenticated() .and() .csrf().disable() .build() 

Konfigurasi yang diberikan menentukan bahwa bagian dari sumber daya web (misalnya, statika) tersedia untuk semua pengguna, termasuk mereka yang belum diautentikasi, dan yang lainnya ( .anyExchange() ) hanya diautentikasi. Jika Anda mencoba memasukkan URL yang memerlukan otentikasi, itu akan diarahkan ke halaman login ( https://localhost/login ):


Halaman ini menggunakan alat kerangka Bootstrap, yang terhubung ke proyek menggunakan Webjars, yang memungkinkan untuk mengelola perpustakaan sisi klien sebagai dependensi reguler. Thymeleaf digunakan untuk membentuk halaman HTML. Akses ke halaman login dikonfigurasikan menggunakan WebFlux:

 @Bean fun routes() = router { GET("/login") { ServerResponse.ok().contentType(MediaType.TEXT_HTML).render("login") } } 

Routing Spring Cloud Gateway dapat dikonfigurasi dalam YAML atau konfigurasi java. Rute ke layanan-layanan mikro ditetapkan secara manual atau dibuat secara otomatis berdasarkan data yang diterima dari registri Layanan. Dengan jumlah UI yang cukup besar yang memerlukan perutean, akan lebih mudah untuk menggunakan integrasi dengan registri Layanan:

 spring: cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true include-expression: serviceId.endsWith('-UI') url-expression: "'lb:http://'+serviceId" 

Nilai parameter include-expression menunjukkan bahwa rute akan dibuat hanya untuk layanan Microsoft yang namanya berakhir dengan -UI , dan nilai parameter url-expression adalah bahwa mereka dapat diakses melalui protokol HTTP, tidak seperti gateway UI yang bekerja melalui HTTPS, dan ketika diakses mereka akan menggunakan penyeimbangan beban klien (diterapkan menggunakan Pita Netflix).

Pertimbangkan contoh membuat rute di konfigurasi java secara manual (tanpa integrasi dengan registri Layanan):

 @Bean fun routeLocator(builder: RouteLocatorBuilder) = builder.routes { route("eureka-gui") { path("/eureka") filters { rewritePath("/eureka", "/") } uri("lb:http://eureka-server") } route("eureka-internals") { path("/eureka/**") uri("lb:http://eureka-server") } } 

Rute rute pertama ke halaman beranda server Eureka yang sebelumnya ditampilkan ( http://localhost:8761 ), yang kedua diperlukan untuk memuat sumber daya di halaman ini.

Semua rute yang dibuat oleh aplikasi tersedia di https://localhost/actuator/gateway/routes .

Dalam layanan microsoft yang mendasari, mungkin perlu untuk mengakses login dan / atau peran pengguna yang diautentikasi di gateway UI. Untuk melakukan ini, saya membuat filter yang menambahkan header yang sesuai ke permintaan:

 @Component class AddCredentialsGlobalFilter : GlobalFilter { private val loggedInUserHeader = "logged-in-user" private val loggedInUserRolesHeader = "logged-in-user-roles" override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain) = exchange.getPrincipal<Principal>() .flatMap { val request = exchange.request.mutate() .header(loggedInUserHeader, it.name) .header(loggedInUserRolesHeader, (it as Authentication).authorities?.joinToString(";") ?: "") .build() chain.filter(exchange.mutate().request(request).build()) } } 

Sekarang mari kita beralih ke UI Item menggunakan gateway UI - https://localhost/items-ui/greeting , dengan benar mengasumsikan bahwa pemrosesan header ini telah diterapkan di UI Item:


Spring Cloud Sleuth adalah solusi untuk penelusuran kueri dalam sistem terdistribusi. Trace Id (pass-through identifier) ​​dan Span Id (unit identifier kerja) ditambahkan ke header permintaan yang melewati beberapa layanan microser (untuk memudahkan pemahaman, saya menyederhanakan skema; berikut ini penjelasan lebih rinci):


Fungsionalitas ini terhubung dengan hanya menambahkan spring-cloud-starter-sleuth .

Dengan menentukan pengaturan pencatatan yang sesuai, di konsol layanan microser yang sesuai, Anda dapat melihat sesuatu seperti berikut (Trace Id dan Span Id ditampilkan setelah nama layanan microser):

 DEBUG [ui-gateway,009b085bfab5d0f2,009b085bfab5d0f2,false] oscghRoutePredicateHandlerMapping : Route matched: CompositeDiscoveryClient_ITEMS-UI DEBUG [items-ui,009b085bfab5d0f2,947bff0ce8d184f4,false] oswrfunction.server.RouterFunctions : Predicate "(GET && /example)" matches against "GET /example" DEBUG [items-service,009b085bfab5d0f2,dd3fa674cd994b01,false] oswrfunction.server.RouterFunctions : Predicate "(GET && /{id})" matches against "GET /1" 

Untuk representasi grafis dari penelusuran terdistribusi, Anda dapat menggunakan, misalnya, Zipkin, yang akan bertindak sebagai server yang mengumpulkan informasi tentang permintaan HTTP dari layanan microser lainnya (detail lebih lanjut di sini ).

Majelis


Bergantung pada OS, gradlew clean build atau ./gradlew clean build .

Mengingat kemungkinan menggunakan pembungkus Gradle , tidak perlu Gradle yang dipasang secara lokal.

Build dan peluncuran selanjutnya berhasil melewati JDK 11.0.1. Sebelum ini, proyek bekerja pada JDK 10, jadi saya berasumsi bahwa pada versi ini tidak akan ada masalah dengan perakitan dan peluncuran. Saya tidak punya data tentang versi JDK sebelumnya. Juga, perlu diingat bahwa Gradle 5 yang digunakan membutuhkan setidaknya JDK 8.

Luncurkan


Saya sarankan memulai aplikasi sesuai dengan yang dijelaskan dalam artikel ini. Jika Anda menggunakan Intellij IDEA dengan Run Dashboard diaktifkan, Anda harus mendapatkan sesuatu seperti berikut:


Kesimpulan


Artikel ini meneliti contoh arsitektur microservice pada tumpukan teknologi saat ini di dunia Java, komponen utamanya dan beberapa fitur. Saya berharap bagi seseorang materi akan bermanfaat. Terima kasih atas perhatian anda!

Referensi


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


All Articles