개발/그 외
[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 객체를 만들어 요청할 수 있다.