From 3df63eb5c4bf26787a93db360eb8e10592eccf28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=20G=C3=B3mez?= Date: Mon, 18 Nov 2019 22:56:01 +0100 Subject: [PATCH] Integrate elasticsearch --- apps/main/resources/.env | 4 + build.gradle | 9 +- doc/endpoints/backoffice_frontend.http | 31 +++++++ docker-compose.yml | 10 +++ .../database/backoffice/courses.json | 20 +++++ .../courses/domain/BackofficeCourse.java | 20 +++++ ...asticsearchBackofficeCourseRepository.java | 39 +++++++++ .../BackofficeElasticsearchConfiguration.java | 83 +++++++++++++++++++ .../elasticsearch/ElasticsearchClient.java | 44 ++++++++++ .../ElasticsearchRepository.java | 44 ++++++++++ 10 files changed, 302 insertions(+), 2 deletions(-) create mode 100644 doc/endpoints/backoffice_frontend.http create mode 100644 src/backoffice/main/resources/database/backoffice/courses.json create mode 100644 src/backoffice/main/tv/codely/backoffice/courses/infrastructure/persistence/ElasticsearchBackofficeCourseRepository.java create mode 100644 src/backoffice/main/tv/codely/backoffice/shared/infrastructure/persistence/BackofficeElasticsearchConfiguration.java create mode 100644 src/shared/main/tv/codely/shared/infrastructure/elasticsearch/ElasticsearchClient.java create mode 100644 src/shared/main/tv/codely/shared/infrastructure/elasticsearch/ElasticsearchRepository.java diff --git a/apps/main/resources/.env b/apps/main/resources/.env index 37601ad6..6a2c543b 100644 --- a/apps/main/resources/.env +++ b/apps/main/resources/.env @@ -15,6 +15,10 @@ BACKOFFICE_DATABASE_PORT=3306 BACKOFFICE_DATABASE_NAME=backoffice BACKOFFICE_DATABASE_USER=root BACKOFFICE_DATABASE_PASSWORD= +# Elasticsearch +BACKOFFICE_ELASTICSEARCH_HOST=127.0.0.1 +BACKOFFICE_ELASTICSEARCH_PORT=9200 +BACKOFFICE_ELASTICSEARCH_INDEX_PREFIX=backoffice # COMMON # #--------------------------------# diff --git a/build.gradle b/build.gradle index 54ffefe6..5b2eadfe 100644 --- a/build.gradle +++ b/build.gradle @@ -16,11 +16,15 @@ allprojects { set('springCloudVersion', "Hoxton.M3") } + ext { + set('elasticsearch.version', '6.8.4') + } + dependencies { // Prod implementation 'org.apache.logging.log4j:log4j-core:2.12.1' + implementation 'org.apache.logging.log4j:log4j-api:2.12.1' implementation 'com.vlkan.log4j2:log4j2-logstash-layout:0.19' - implementation 'io.github.cdimascio:java-dotenv:5.1.3' implementation 'org.hibernate:hibernate-core:5.4.5.Final' @@ -30,7 +34,8 @@ allprojects { implementation 'javax.xml.bind:jaxb-api:2.3.1' implementation 'org.reflections:reflections:0.9.11' implementation 'org.springframework.boot:spring-boot-starter-amqp' - + implementation 'org.elasticsearch.client:elasticsearch-rest-client:6.8.4' + implementation 'org.elasticsearch.client:elasticsearch-rest-high-level-client:6.8.4' runtime 'mysql:mysql-connector-java:8.0.17' // Test diff --git a/doc/endpoints/backoffice_frontend.http b/doc/endpoints/backoffice_frontend.http new file mode 100644 index 00000000..5ec56ecf --- /dev/null +++ b/doc/endpoints/backoffice_frontend.http @@ -0,0 +1,31 @@ +# ELASTIC - Search +POST localhost:9200/backoffice_courses/_search +Content-Type: application/json + +{ + "query": { + "term": { + "name": "Pepe" + } + } +} + +### +# ELASTIC - Search +POST localhost:9200/backoffice_courses/_search +Content-Type: application/json + +### + +PUT localhost:9200/backoffice_courses/_settings +Content-Type: application/json + +{ + "index": { + "blocks": { + "read_only_allow_delete": "false" + } + } +} + +### diff --git a/docker-compose.yml b/docker-compose.yml index 3cad6b76..04313334 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,6 +27,16 @@ services: - RABBITMQ_DEFAULT_USER=codelytv - RABBITMQ_DEFAULT_PASS=c0d3ly + elasticsearch: + container_name: codelytv-java_ddd_skeleton-elasticsearch + image: 'elasticsearch:6.8.4' + restart: unless-stopped + ports: + - 9300:9300 + - 9200:9200 + environment: + - discovery.type=single-node + java: container_name: codelytv-ddd_skeleton-java build: diff --git a/src/backoffice/main/resources/database/backoffice/courses.json b/src/backoffice/main/resources/database/backoffice/courses.json new file mode 100644 index 00000000..bbf66139 --- /dev/null +++ b/src/backoffice/main/resources/database/backoffice/courses.json @@ -0,0 +1,20 @@ +{ + "mappings": { + "courses": { + "properties": { + "id": { + "type": "keyword", + "index": true + }, + "name": { + "type": "text", + "index": true + }, + "duration": { + "type": "text", + "index": true + } + } + } + } +} diff --git a/src/backoffice/main/tv/codely/backoffice/courses/domain/BackofficeCourse.java b/src/backoffice/main/tv/codely/backoffice/courses/domain/BackofficeCourse.java index 0b163042..ff468396 100644 --- a/src/backoffice/main/tv/codely/backoffice/courses/domain/BackofficeCourse.java +++ b/src/backoffice/main/tv/codely/backoffice/courses/domain/BackofficeCourse.java @@ -1,5 +1,9 @@ package tv.codely.backoffice.courses.domain; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + public final class BackofficeCourse { private final String id; private final String name; @@ -17,6 +21,14 @@ public BackofficeCourse(String id, String name, String duration) { this.duration = duration; } + public static BackofficeCourse fromPrimitives(Map plainData) { + return new BackofficeCourse( + (String) plainData.get("id"), + (String) plainData.get("name"), + (String) plainData.get("duration") + ); + } + public String id() { return id; } @@ -28,4 +40,12 @@ public String name() { public String duration() { return duration; } + + public HashMap toPrimitives() { + return new HashMap() {{ + put("id", id); + put("name", name); + put("duration", duration); + }}; + } } diff --git a/src/backoffice/main/tv/codely/backoffice/courses/infrastructure/persistence/ElasticsearchBackofficeCourseRepository.java b/src/backoffice/main/tv/codely/backoffice/courses/infrastructure/persistence/ElasticsearchBackofficeCourseRepository.java new file mode 100644 index 00000000..b1710959 --- /dev/null +++ b/src/backoffice/main/tv/codely/backoffice/courses/infrastructure/persistence/ElasticsearchBackofficeCourseRepository.java @@ -0,0 +1,39 @@ +package tv.codely.backoffice.courses.infrastructure.persistence; + +import org.springframework.context.annotation.Primary; +import tv.codely.backoffice.courses.domain.BackofficeCourse; +import tv.codely.backoffice.courses.domain.BackofficeCourseRepository; +import tv.codely.shared.domain.Service; +import tv.codely.shared.domain.criteria.Criteria; +import tv.codely.shared.infrastructure.elasticsearch.ElasticsearchClient; +import tv.codely.shared.infrastructure.elasticsearch.ElasticsearchRepository; + +import java.util.List; + +@Primary +@Service +public final class ElasticsearchBackofficeCourseRepository extends ElasticsearchRepository implements BackofficeCourseRepository { + public ElasticsearchBackofficeCourseRepository(ElasticsearchClient client) { + super(client); + } + + @Override + public void save(BackofficeCourse course) { + persist(course.id(), course.toPrimitives()); + } + + @Override + public List searchAll() { + return searchAllInElastic(BackofficeCourse::fromPrimitives); + } + + @Override + public List matching(Criteria criteria) { + return searchAllInElastic(BackofficeCourse::fromPrimitives); + } + + @Override + protected String moduleName() { + return "courses"; + } +} diff --git a/src/backoffice/main/tv/codely/backoffice/shared/infrastructure/persistence/BackofficeElasticsearchConfiguration.java b/src/backoffice/main/tv/codely/backoffice/shared/infrastructure/persistence/BackofficeElasticsearchConfiguration.java new file mode 100644 index 00000000..44d14690 --- /dev/null +++ b/src/backoffice/main/tv/codely/backoffice/shared/infrastructure/persistence/BackofficeElasticsearchConfiguration.java @@ -0,0 +1,83 @@ +package tv.codely.backoffice.shared.infrastructure.persistence; + +import org.apache.http.HttpHost; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.indices.CreateIndexRequest; +import org.elasticsearch.client.indices.GetIndexRequest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.ResourcePatternResolver; +import tv.codely.shared.infrastructure.config.Parameter; +import tv.codely.shared.infrastructure.config.ParameterNotExist; +import tv.codely.shared.infrastructure.elasticsearch.ElasticsearchClient; + +import java.io.IOException; +import java.util.Objects; +import java.util.Scanner; + +@Configuration +public class BackofficeElasticsearchConfiguration { + private final Parameter config; + private final ResourcePatternResolver resourceResolver; + + public BackofficeElasticsearchConfiguration(Parameter config, ResourcePatternResolver resourceResolver) { + this.config = config; + this.resourceResolver = resourceResolver; + } + + @Bean + public ElasticsearchClient elasticsearchClient() throws ParameterNotExist, IOException { + ElasticsearchClient client = new ElasticsearchClient( + new RestHighLevelClient( + RestClient.builder( + new HttpHost( + config.get("BACKOFFICE_ELASTICSEARCH_HOST"), + config.getInt("BACKOFFICE_ELASTICSEARCH_PORT"), + "http" + ) + ) + ), + RestClient.builder( + new HttpHost( + config.get("BACKOFFICE_ELASTICSEARCH_HOST"), + config.getInt("BACKOFFICE_ELASTICSEARCH_PORT"), + "http" + )).build(), + config.get("BACKOFFICE_ELASTICSEARCH_INDEX_PREFIX") + ); + + generateIndexIfNotExists(client, "backoffice"); + + return client; + } + + private void generateIndexIfNotExists(ElasticsearchClient client, String contextName) throws IOException { + Resource[] jsonsIndexes = resourceResolver.getResources( + String.format("classpath:database/%s/*.json", contextName) + ); + + for (Resource jsonIndex : jsonsIndexes) { + String indexName = Objects.requireNonNull(jsonIndex.getFilename()).replace(".json", ""); + + if (!indexExists(indexName, client)) { + String indexBody = new Scanner( + jsonIndex.getInputStream(), + "UTF-8" + ).useDelimiter("\\A").next(); + + Request request = new Request("PUT", indexName); + request.setJsonEntity(indexBody); + + client.lowLevelClient().performRequest(request); + } + } + } + + private boolean indexExists(String indexName, ElasticsearchClient client) throws IOException { + return client.highLevelClient().indices().exists(new GetIndexRequest(indexName), RequestOptions.DEFAULT); + } +} diff --git a/src/shared/main/tv/codely/shared/infrastructure/elasticsearch/ElasticsearchClient.java b/src/shared/main/tv/codely/shared/infrastructure/elasticsearch/ElasticsearchClient.java new file mode 100644 index 00000000..10b9916c --- /dev/null +++ b/src/shared/main/tv/codely/shared/infrastructure/elasticsearch/ElasticsearchClient.java @@ -0,0 +1,44 @@ +package tv.codely.shared.infrastructure.elasticsearch; + +import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestHighLevelClient; + +import java.io.IOException; +import java.io.Serializable; +import java.util.HashMap; + +public final class ElasticsearchClient { + private final RestHighLevelClient highLevelClient; + private final RestClient lowLevelClient; + private final String indexPrefix; + + public ElasticsearchClient(RestHighLevelClient highLevelClient, RestClient lowLevelClient, String indexPrefix) { + this.highLevelClient = highLevelClient; + this.lowLevelClient = lowLevelClient; + this.indexPrefix = indexPrefix; + } + + public RestHighLevelClient highLevelClient() { + return highLevelClient; + } + + public RestClient lowLevelClient() { + return lowLevelClient; + } + + public String indexPrefix() { + return indexPrefix; + } + + public void persist(String moduleName, String id, HashMap plainBody) throws IOException { + IndexRequest request = new IndexRequest(indexFor(moduleName), moduleName, id).source(plainBody); + + highLevelClient().index(request, RequestOptions.DEFAULT); + } + + public String indexFor(String moduleName) { + return String.format("%s_%s", indexPrefix(), moduleName); + } +} diff --git a/src/shared/main/tv/codely/shared/infrastructure/elasticsearch/ElasticsearchRepository.java b/src/shared/main/tv/codely/shared/infrastructure/elasticsearch/ElasticsearchRepository.java new file mode 100644 index 00000000..e36c64de --- /dev/null +++ b/src/shared/main/tv/codely/shared/infrastructure/elasticsearch/ElasticsearchRepository.java @@ -0,0 +1,44 @@ +package tv.codely.shared.infrastructure.elasticsearch; + +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.client.RequestOptions; + +import java.io.IOException; +import java.io.Serializable; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +public abstract class ElasticsearchRepository { + private final ElasticsearchClient client; + + public ElasticsearchRepository(ElasticsearchClient client) { + this.client = client; + } + + abstract protected String moduleName(); + + protected List searchAllInElastic(Function, T> unserializer) { + SearchRequest request = new SearchRequest(client.indexFor(moduleName())); + try { + SearchResponse response = client.highLevelClient().search(request, RequestOptions.DEFAULT); + + return Arrays.stream(response.getHits().getHits()) + .map(hit -> unserializer.apply(hit.getSourceAsMap())) + .collect(Collectors.toList()); + } catch (IOException e) { + e.printStackTrace(); + } + + return Collections.emptyList(); + } + + protected void persist(String id, HashMap plainBody) { + try { + client.persist(moduleName(), id, plainBody); + } catch (IOException e) { + e.printStackTrace(); + } + } +}