ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [OpenSearch] Spring - Kotlin 기반의 OpenSearch Client 구현
    개발/그 외 2023. 11. 3. 23:13

    1. 개요

    • ElasticSearch 에 비해 AWS OpenSearch는 JAVA API Client 문서가 불친절한 느낌이다.
    • 이것 저것 해보면서 겪었던 시행 착오들을 기록하고,
    • SpringBoot - Kotlin 기반으로 AWS OpenSearch Instance에 접근하고 색인/검색 요청을 하는 JAVA API Client 설정 및 구현 방법을 정리한다.

    2. Dependency

    dependencies {
    	...
    	implementation("org.opensearch.client:opensearch-rest-client:2.11.0")
    	implementation("org.opensearch.client:opensearch-java:2.7.0")
    	implementation("jakarta.json:jakarta.json-api:2.0.1")
        	...
     }
    • jakarta.json-api 의존성을 추가해주지 않으면,
      • java.lang.NoClassDefFoundError: jakarta/json/JsonException 에러가 난다.
      • = ES에서 가져온 JSON 기반의 객체를 파싱하는데 실패하는 것 같다.

    3. OpenSearchClient

    import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
    import org.apache.http.HttpHost
    import org.apache.http.auth.AuthScope
    import org.apache.http.auth.UsernamePasswordCredentials
    import org.apache.http.impl.client.BasicCredentialsProvider
    import org.opensearch.client.RestClient
    import org.opensearch.client.json.jackson.JacksonJsonpMapper
    import org.opensearch.client.opensearch.OpenSearchClient
    import org.opensearch.client.transport.rest_client.RestClientTransport
    import org.springframework.context.annotation.Bean
    import org.springframework.context.annotation.Configuration
    
    @Configuration
    class OpenSearchConfiguration {
    
        @Bean
        fun openSearchClient(): OpenSearchClient {
            val host = HttpHost("127.0.0.1", 9200, "https") // OpenSearch Domain
            val credential = BasicCredentialsProvider().apply {
                this.setCredentials(
                    AuthScope(host),
                    UsernamePasswordCredentials("admin", "admin") // OpenSearch Username & Password
                )
            }
    
            val restClient = RestClient.builder(host).setHttpClientConfigCallback {
                it.setDefaultCredentialsProvider(credential)
            }.build()
    
            return OpenSearchClient(RestClientTransport(restClient, JacksonJsonpMapper(jacksonObjectMapper())))
        }
    }
    • Webflux를 사용한다면 OpenSearchAsyncClient로 생성하여 비동기로 OpenSearch 호출이 가능하다.
    • JacksonJsonpMapper도 jacksonObjectMapper로 만들어주자. (안하면 parsing Exception 발생)

    4. Service Example

    import org.opensearch.client.opensearch.OpenSearchClient
    import org.opensearch.client.opensearch.core.BulkRequest
    import org.opensearch.client.opensearch.core.SearchRequest
    import org.opensearch.client.opensearch.core.bulk.CreateOperation
    import org.opensearch.client.opensearch.indices.CreateIndexRequest
    import org.springframework.stereotype.Service
    
    @Service
    class OpenSearchService(
        private val openSearchClient: OpenSearchClient
    ) {
    
        /**
         * 인덱스 생성
          */
        fun createIndex() {
            try {
                val indexName = "test-index"
    
                val request = CreateIndexRequest.of {
                    it.index(indexName)
                }
    
                openSearchClient.indices().create(request)
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    
        /**
         * bulk index(색인)
         */
        fun bulk() {
            val indexName = "test-index"
            val dummyData = listOf(
                TestIndex(
                    id = "test1",
                    name = "test1",
                    age = 20
                ),
                TestIndex(
                    id = "test2",
                    name = "test2",
                    age = 30
                )
            )
            try {
                val bulkRequest = BulkRequest.Builder()
    
                dummyData.forEach {  testIndex ->
                    bulkRequest.operations { operation ->
                        operation.create { co: CreateOperation.Builder<TestIndex> ->
                            co.index(indexName).id(testIndex.id).document(testIndex)
                        }
                    }
                }
    
                openSearchClient.bulk(bulkRequest.build())
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    
        /**
         * 검색
         */
        fun search(keyword: String): List<TestIndex> {
            val indexName = "test-index"
            return try {
                val request = SearchRequest.of { searchRequest ->
                    searchRequest.index(indexName)
                    searchRequest.query { query ->
                        query.match { matchQuery ->
                            matchQuery.field("name")
                            matchQuery.query {
                                it.stringValue(keyword)
                            }
                        }
                    }
                }
    
                openSearchClient.search(request, TestIndex::class.java).hits().hits().mapNotNull {
                    it.source()
                }
            } catch (e: Exception) {
                e.printStackTrace()
                emptyList()
            }
        }
    
        data class TestIndex(
            val id: String,
            val name: String,
            val age: Int,
        )
    }
    • 인덱스 생성, 색인, 검색 예제
    • 간단하게 request 객체를 만들어 요청할 수 있다.
Designed by Tistory.