Pasang Iklan Atas 1

Down Menu

Drop Down MenusCSS Drop Down MenuPure CSS Dropdown Menu

Tuesday, October 23, 2018

Introduction to Reactive APIs With Postgres, R2DBC, Spring Data JDBC and Spring WebFlux

Saya tahu - ada BANYAK teknologi yang tercantum dalam judul artikel ini. Spring WebFlux telah diperkenalkan dengan Spring 5 dan Spring Boot 2 sebagai proyek untuk membangun aplikasi web reaktif-stack. Saya telah menjelaskan cara menggunakannya bersama Spring Boot dan Spring Cloud untuk membangun microservices reaktif dalam artikel itu: Reactive Microservices dengan Spring WebFlux dan Spring Cloud. Spring 5 juga telah memperkenalkan proyek-proyek yang mendukung akses reaktif ke database NoSQL, seperti Cassandra, MongoDB, atau Couchbase. Namun, masih ada kurangnya dukungan untuk reaktif untuk menyediakan akses ke database relasional. Perubahan ini datang bersamaan dengan proyek R2DBC (Reactive Relational Database Connectivity). Proyek itu juga sedang dikembangkan oleh anggota Pivotal. Tampaknya ini adalah inisiatif yang sangat menarik, namun pada awal perjalanan. Bagaimanapun, ada modul untuk integrasi dengan Postgres, dan kami akan menggunakannya untuk aplikasi demo kami.
R2DBC tidak akan menjadi satu-satunya solusi baru yang menarik yang dijelaskan dalam artikel ini. Saya juga akan menunjukkan kepada Anda bagaimana menggunakan Spring Data JDBC - proyek lain yang sangat menarik yang dirilis baru-baru ini. Perlu disebutkan fitur Spring Data JDBC. Proyek ini telah dirilis dan tersedia di bawah versi 1.0. Ini adalah bagian dari kerangka Data Musim Semi yang lebih besar. Ini menawarkan abstraksi repositori berdasarkan JDBC. Alasan utama pembuatan pustaka tersebut adalah untuk mengizinkan akses ke basis data relasional menggunakan Data Musim Semi (melalui antarmuka CrudRepository) tanpa menyertakan pustaka JPA ke dependensi aplikasi. Tentu saja, JPA masih tetap merupakan API persistensi utama yang digunakan untuk aplikasi Java. Spring Data JDBC bertujuan untuk menjadi lebih sederhana secara konseptual daripada JPA dengan tidak menerapkan pola populer seperti pemuatan malas, cache, konteks kotor, dan sesi. Ini juga memberikan dukungan yang sangat terbatas untuk pemetaan berbasiskan anotasi. Akhirnya, ia menyediakan implementasi repositori reaktif yang menggunakan R2DBC untuk mengakses database relasional. Meskipun modul itu masih dalam pengembangan (hanya versi SNAPSHOT tersedia), kami akan mencoba menggunakannya dalam aplikasi demo kami. Mari lanjutkan ke penerapan.

Including Dependencies

Kami menggunakan Kotlin untuk implementasi. Jadi pertama, kami menyertakan beberapa dependensi Kotlin yang diperlukan.
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib</artifactId>
    <version>${kotlin.version}</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-test-junit</artifactId>
    <version>${kotlin.version}</version>
    <scope>test</scope>
</dependency>

We should also add kotlin-maven-plugin with support for Spring.
<plugin>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-maven-plugin</artifactId>
    <version>${kotlin.version}</version>
    <executions>
        <execution>
            <id>compile</id>
            <phase>compile</phase>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
        <execution>
            <id>test-compile</id>
            <phase>test-compile</phase>
            <goals>
                <goal>test-compile</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <args>
            <arg>-Xjsr305=strict</arg>
        </args>
        <compilerPlugins>
            <plugin>spring</plugin>
        </compilerPlugins>
    </configuration>
</plugin>

Then, we may proceed to include frameworks required for the demo implementation. We need to include the special SNAPSHOT version of Spring Data JDBC dedicated for accessing the database using R2DBC. We also have to add R2DBC libraries and Spring WebFlux. As you may see below, only Spring WebFlux is available in a stable version (as a part of Spring Boot RELEASE).
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jdbc</artifactId>
    <version>1.0.0.r2dbc-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>io.r2dbc</groupId>
    <artifactId>r2dbc-spi</artifactId>
    <version>1.0.0.M5</version>
</dependency>
<dependency>
    <groupId>io.r2dbc</groupId>
    <artifactId>r2dbc-postgresql</artifactId>
    <version>1.0.0.M5</version>
</dependency>

Kami juga harus menambahkan kotlin-maven-plugin dengan dukungan untuk Spring.
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-releasetrain</artifactId>
            <version>Lovelace-RELEASE</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencyManagement>

Repositories

Kami menggunakan gaya Spring Data yang terkenal dari implementasi repositori CRUD. Dalam hal ini, kita perlu membuat antarmuka yang memperluas antarmuka ReactiveCrudRepository.
Berikut penerapan repositori untuk mengelola objek Karyawan.
interface EmployeeRepository : ReactiveCrudRepository<Employee, Int< {
    @Query("select id, name, salary, organization_id from employee e where e.organization_id = $1")
    fun findByOrganizationId(organizationId: Int) : Flux<Employee>
}

Berikut ini implementasi lain dari repositori - kali ini, kami menggunakannya untuk mengelola Organisasi objek.
interface OrganizationRepository : ReactiveCrudRepository<Organization, Int< {
}

Implementing Entities and DTOs

Kotlin menyediakan cara mudah untuk menciptakan kelas entitas dengan mendeklarasikannya sebagai kelas data. Ketika menggunakan Spring Data JDBC, kita harus menetapkan kunci utama untuk entitas dengan annotating field dengan @Id. Ini mengasumsikan kunci secara otomatis bertambah oleh database. Jika Anda tidak menggunakan kolom peningkatan otomatis, Anda harus menggunakan pendengar BeforeSaveEvent, yang menetapkan ID entitas. Namun, saya mencoba mengatur pendengar semacam itu untuk entitas saya, tetapi itu tidak bekerja dengan versi Spring Data JDBC yang reaktif.
Berikut ini penerapan kelas entitas Karyawan. Perlu disebutkan bahwa Spring Data JDBC akan secara otomatis memetakan organisasi bidang kelasId ke dalam kolom basis data organization_id.
data class Employee(val name: String, val salary: Int, val organizationId: Int) {
    @Id 
    var id: Int? = null
}

Berikut ini penerapan kelas entitas Organisasi.
data class Organization(var name: String) {
    @Id 
    var id: Int? = null
}

R2DBC tidak mendukung daftar atau kumpulan apa pun. Karena saya ingin kembali daftar dengan karyawan di dalam Organisasi objek di salah satu titik akhir API, saya telah membuat DTO yang berisi daftar seperti itu, seperti yang ditunjukkan di bawah ini.
data class OrganizationDTO(var id: Int?, var name: String) {
    var employees : MutableList = ArrayList()
    constructor(employees: MutableList) : this(null, "") {
        this.employees = employees
    }
}

Skrip SQL yang terkait dengan entitas yang dibuat terlihat di bawah ini. Seri tipe bidang akan secara otomatis membuat urutan dan melampirkannya ke ID bidang.
CREATE TABLE employee (
    name character varying NOT NULL,
    salary integer NOT NULL,
    id serial PRIMARY KEY,
    organization_id integer
);
CREATE TABLE organization (
    name character varying NOT NULL,
    id serial PRIMARY KEY
);

Building Sample Web Applications

Untuk tujuan demo, kami akan membangun dua aplikasi independen: layanan karyawan dan layanan organisasi. Aplikasi organisasi-layanan berkomunikasi dengan karyawan-layanan menggunakan WebFlux WebClient. Ia mendapat daftar karyawan yang ditugaskan ke organisasi dan termasuk mereka untuk merespon bersama dengan OrganisasiOryek. Contoh kode sumber aplikasi tersedia di GitHub di bawah repositori sample-spring-data-webflux.
Oke, mari kita mulai dari mendeklarasikan kelas utama Spring Boot. Kita perlu mengaktifkan repositori Spring Data JDBC dengan memberi annotating kelas utama dengan @EnableJdbcRepositories.
@SpringBootApplication
@EnableJdbcRepositories
class EmployeeApplication
fun main(args: Array<String>) {
    runApplication<EmployeeApplication>(*args)
}

Bekerja dengan R2DBC dan Postgres memerlukan beberapa konfigurasi. Karena tahap awal kemajuan dalam pengembangan Spring Data JDBC dan R2DBC, tidak ada konfigurasi otomatis Spring Boot untuk Postgres. Kita perlu mendeklarasikan pabrik koneksi, klien, dan repositori di dalam kacang @Configuration.
@Configuration
class EmployeeConfiguration {
    @Bean
    fun repository(factory: R2dbcRepositoryFactory): EmployeeRepository {
        return factory.getRepository(EmployeeRepository::class.java)
    }
    @Bean
    fun factory(client: DatabaseClient): R2dbcRepositoryFactory {
        val context = RelationalMappingContext()
        context.afterPropertiesSet()
        return R2dbcRepositoryFactory(client, context)
    }
    @Bean
    fun databaseClient(factory: ConnectionFactory): DatabaseClient {
        return DatabaseClient.builder().connectionFactory(factory).build()
    }
    @Bean
    fun connectionFactory(): PostgresqlConnectionFactory {
        val config = PostgresqlConnectionConfiguration.builder() //
                .host("192.168.99.100") //
                .port(5432) //
                .database("reactive") //
                .username("reactive") //
                .password("reactive123") //
                .build()
        return PostgresqlConnectionFactory(config)
    }
}

Akhirnya, kita dapat membuat pengendali REST yang berisi definisi metode API reaktif kami. Dengan Kotlin, tidak butuh banyak ruang. Definisi kontroler berikut berisi tiga metode GET yang memungkinkan kita untuk menemukan semua karyawan, semua karyawan yang ditugaskan ke organisasi tertentu, atau satu karyawan berdasarkan id.

@RestController
@RequestMapping("/employees")
class EmployeeController {
    @Autowired
    lateinit var repository : EmployeeRepository
    @GetMapping
    fun findAll() : Flux<Employee> = repository.findAll()
    @GetMapping("/{id}")
    fun findById(@PathVariable id : Int) : Mono<Employee> = repository.findById(id)
    @GetMapping("/organization/{organizationId}")
    fun findByorganizationId(@PathVariable organizationId : Int) : Flux<Employee> = repository.findByOrganizationId(organizationId)
    @PostMapping
    fun add(@RequestBody employee: Employee) : Mono<Employee> = repository.save(employee)
}

Inter-Service Communication

Untuk OrganizationController, penerapannya sedikit lebih rumit. Karena organisasi-layanan berkomunikasi dengan karyawan-layanan, pertama-tama kita perlu menyatakan WebFlibuilder WebFlux reaktif.
@Bean
fun clientBuilder() : WebClient.Builder {
    return WebClient.builder()
}

Kemudian, mirip dengan repository bean, pembangun sedang disuntikkan ke pengontrol. Ini digunakan di dalam metode the findByIdWithEmployees untuk memanggil metode GET /employees/organization/{organizationId} terpapar oleh employee-service. Seperti yang Anda lihat pada fragmen kode di bawah ini, ia menyediakan API reaktif dan mengembalikan objek Flux yang berisi daftar karyawan yang ditemukan. Daftar ini disuntikkan ke dalam OrganizationDTOobject menggunakan metode zipWith Reactor.
@RestController
@RequestMapping("/organizations")
class OrganizationController {
    @Autowired
    lateinit var repository : OrganizationRepository
    @Autowired
    lateinit var clientBuilder : WebClient.Builder
    @GetMapping
    fun findAll() : Flux<Organization> = repository.findAll()
    @GetMapping("/{id}")
    fun findById(@PathVariable id : Int) : Mono<Organization> = repository.findById(id)
    @GetMapping("/{id}/withEmployees")
    fun findByIdWithEmployees(@PathVariable id : Int) : Mono<OrganizationDTO> {
        val employees : Flux<Employee> = clientBuilder.build().get().uri("http://localhost:8090/employees/organization/$id")
                .retrieve().bodyToFlux(Employee::class.java)
        val org : Mono = repository.findById(id)
        return org.zipWith(employees.collectList())
                .map { tuple -> OrganizationDTO(tuple.t1.id as Int, tuple.t1.name, tuple.t2) }
    }
    @PostMapping
    fun add(@RequestBody employee: Organization) : Mono<Organization> = repository.save(employee)
}

How Does it Work?

Sebelum menjalankan tes, kita perlu memulai basis data Postgres. Berikut adalah perintah Docker yang digunakan untuk menjalankan kontainer Postgres. Ini membuat pengguna dengan kata sandi, dan mengatur database default.
$ docker run -d --name postgres -p 5432:5432 -e POSTGRES_USER=reactive -e POSTGRES_PASSWORD=reactive123 -e POSTGRES_DB=reactive postgres

Kemudian, kita perlu membuat beberapa tabel pengujian, jadi Anda harus menjalankan skrip SQL yang ditempatkan di bagian Entitas Implementasi dan DTO. Setelah itu, Anda dapat memulai aplikasi uji kami. Jika Anda tidak mengesampingkan pengaturan default yang disediakan di dalam file application.yml, layanan karyawan mendengarkan pada port 8090 dan layanan organisasi pada port 8095. Gambar berikut mengilustrasikan arsitektur sistem sampel kami.
spring-data-1
Sekarang, mari tambahkan beberapa data pengujian menggunakan API reaktif yang diekspos oleh aplikasi.
$ curl -d '{"name":"Test1"}' -H "Content-Type: application/json" -X POST http://localhost:8095/organizations
$ curl -d '{"name":"Name1", "balance":5000, "organizationId":1}' -H "Content-Type: application/json" -X POST http://localhost:8090/employees
$ curl -d '{"name":"Name2", "balance":10000, "organizationId":1}' -H "Content-Type: application/json" -X POST http://localhost:8090/employees

Terakhir, Anda dapat memanggil metode GET organizations / {id} / withEmployees, misalnya, menggunakan browser web Anda. Hasilnya harus serupa dengan hasil yang terlihat pada gambar berikut.
spring-data-2

No comments :

Post a Comment

Terimakasih sudah berkomentar