Skip to content

Commit

Permalink
elastic fuzzy-search is implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
musab.bozkurt committed Feb 14, 2024
1 parent ffec9fa commit a10ac17
Show file tree
Hide file tree
Showing 11 changed files with 144 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.mb.livedataservice.api.controller;

import com.mb.livedataservice.api.filter.ApiCarFilter;
import com.mb.livedataservice.api.request.ApiCarRequest;
import com.mb.livedataservice.api.response.ApiCarResponse;
import com.mb.livedataservice.mapper.CarMapper;
Expand All @@ -10,6 +11,8 @@
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Slf4j
@RestController
@RequiredArgsConstructor
Expand Down Expand Up @@ -42,4 +45,10 @@ public void delete(@PathVariable String id) {
log.info("Received a request to delete. delete - id: {}", id);
carService.deleteCarById(id);
}

@GetMapping("/fuzzy-search")
public List<ApiCarResponse> fuzzySearch(ApiCarFilter apiCarFilter) {
log.info("Received a request to do fuzzy search. fuzzySearch - ApiCarFilter: {}", apiCarFilter);
return carMapper.map(carService.fuzzySearch(carMapper.map(apiCarFilter)));
}
}
19 changes: 19 additions & 0 deletions src/main/java/com/mb/livedataservice/api/filter/ApiCarFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.mb.livedataservice.api.filter;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ApiCarFilter {

private String model;

private Integer yearOfManufacture;

private String brand;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.mb.livedataservice.config;

import com.mb.livedataservice.utils.RedisConstants;
import com.mb.livedataservice.util.RedisConstants;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/com/mb/livedataservice/data/filter/CarFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.mb.livedataservice.data.filter;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CarFilter {

private String model;

private Integer yearOfManufacture;

private String brand;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
public record Car(@Id
String id,

@Field(type = FieldType.Text, name = "model") String model,
@Field(type = FieldType.Text, name = "model")
String model,

@Field(type = FieldType.Integer, name = "year")
Integer yearOfManufacture,
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/com/mb/livedataservice/mapper/CarMapper.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
package com.mb.livedataservice.mapper;

import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.mb.livedataservice.api.filter.ApiCarFilter;
import com.mb.livedataservice.api.request.ApiCarRequest;
import com.mb.livedataservice.api.response.ApiCarResponse;
import com.mb.livedataservice.data.filter.CarFilter;
import com.mb.livedataservice.data.model.elastic.Car;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

import java.util.ArrayList;
import java.util.List;

@Mapper(componentModel = "spring")
public interface CarMapper {

ApiCarResponse map(Car car);

@Mapping(target = "id", ignore = true)
Car map(ApiCarRequest apiCarRequest);

CarFilter map(ApiCarFilter apiCarFilter);

default List<ApiCarResponse> map(SearchResponse<Car> searchResponse) {
List<ApiCarResponse> carResponses = new ArrayList<>();
for (Hit<Car> hit : searchResponse.hits().hits()) {
carResponses.add(this.map(hit.source()));
}
return carResponses;
}
}
4 changes: 4 additions & 0 deletions src/main/java/com/mb/livedataservice/service/CarService.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.mb.livedataservice.service;

import co.elastic.clients.elasticsearch.core.SearchResponse;
import com.mb.livedataservice.data.filter.CarFilter;
import com.mb.livedataservice.data.model.elastic.Car;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
Expand All @@ -13,4 +15,6 @@ public interface CarService {
Page<Car> findAll(Pageable pageable);

void deleteCarById(String id);

SearchResponse<Car> fuzzySearch(CarFilter carFilter);
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
package com.mb.livedataservice.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import com.mb.livedataservice.data.filter.CarFilter;
import com.mb.livedataservice.data.model.elastic.Car;
import com.mb.livedataservice.data.repository.CarRepository;
import com.mb.livedataservice.exception.BaseException;
import com.mb.livedataservice.exception.LiveDataErrorCode;
import com.mb.livedataservice.service.CarService;
import com.mb.livedataservice.util.ElasticSearchUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.function.Supplier;

@Slf4j
@Service
@RequiredArgsConstructor
public class CarServiceImpl implements CarService {

private final CarRepository carRepository;
private final ElasticsearchClient elasticsearchClient;

@Override
public Car save(Car car) {
Expand All @@ -37,4 +46,14 @@ public Page<Car> findAll(Pageable pageable) {
public void deleteCarById(String id) {
carRepository.deleteById(id);
}

@Override
public SearchResponse<Car> fuzzySearch(CarFilter carFilter) {
Supplier<Query> supplier = ElasticSearchUtil.createSupplierQuery(carFilter);
try {
return elasticsearchClient.search(s -> s.index("car_index").query(supplier.get()), Car.class);
} catch (IOException e) {
throw new BaseException(LiveDataErrorCode.UNEXPECTED_ERROR);
}
}
}
38 changes: 38 additions & 0 deletions src/main/java/com/mb/livedataservice/util/ElasticSearchUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.mb.livedataservice.util;

import co.elastic.clients.elasticsearch._types.query_dsl.FuzzyQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import com.mb.livedataservice.data.filter.CarFilter;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.val;
import org.apache.commons.lang3.StringUtils;

import java.util.Objects;
import java.util.function.Supplier;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ElasticSearchUtil {

public static Supplier<Query> createSupplierQuery(CarFilter carFilter) {
return () -> Query.of(builder -> builder.fuzzy(createFuzzyQuery(carFilter)));
}

private static FuzzyQuery createFuzzyQuery(CarFilter carFilter) {
val fuzzyQuery = new FuzzyQuery.Builder();

if (StringUtils.isNotBlank(carFilter.getModel())) {
fuzzyQuery.field("model").value(carFilter.getModel());
}

if (Objects.nonNull(carFilter.getYearOfManufacture())) {
fuzzyQuery.field("yearOfManufacture").value(carFilter.getYearOfManufacture());
}

if (StringUtils.isNotBlank(carFilter.getBrand())) {
fuzzyQuery.field("brand").value(carFilter.getBrand());
}

return fuzzyQuery.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.mb.livedataservice.utils;
package com.mb.livedataservice.util;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.mb.livedataservice.api.request.ApiTutorialRequest;
import com.mb.livedataservice.api.request.ApiTutorialUpdateRequest;
import com.mb.livedataservice.api.response.ApiCarResponse;
import com.mb.livedataservice.api.response.ApiTutorialResponse;
import com.mb.livedataservice.base.BaseUnitTest;
import com.mb.livedataservice.client.jsonplaceholder.DeclarativeJSONPlaceholderRestClient;
Expand Down Expand Up @@ -35,7 +36,9 @@
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -244,4 +247,16 @@ void shouldCreateNewPost() {
PostResponse post = declarativeJSONPlaceholderRestClient.createPost(newPost);
assertThat(post.title()).isEqualTo("new title");
}

@Test
void shouldDoFuzzySearchByFilter() {
Map<String, Object> queryParams = new HashMap<>();
queryParams.put("model", "model");
queryParams.put("yearOfManufacture", "2000");
queryParams.put("brand", "brand");

ApiCarResponse[] tutorials = restTemplate.getForObject("/cars/fuzzy-search?model={model}&yearOfManufacture={yearOfManufacture}&brand={brand}", ApiCarResponse[].class, queryParams);

assertThat(tutorials.length).isNotNegative();
}
}

0 comments on commit a10ac17

Please sign in to comment.