ADD: Basic solution

This commit is contained in:
dev-aritra
2020-11-16 16:14:00 +05:30
parent d7e62c6786
commit a36b821fc7
18 changed files with 325 additions and 4 deletions

View File

@@ -2,12 +2,14 @@ package tech.aritra.springhexagonal;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;
@SpringBootApplication
@EnableReactiveMongoRepositories
public class SpringHexagonalApplication {
public static void main(String[] args) {
SpringApplication.run(SpringHexagonalApplication.class, args);
}
public static void main(String[] args) {
SpringApplication.run(SpringHexagonalApplication.class, args);
}
}

View File

@@ -0,0 +1,34 @@
package tech.aritra.springhexagonal.adapters.persistance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import tech.aritra.springhexagonal.adapters.persistance.entity.StoreEntity;
import tech.aritra.springhexagonal.adapters.persistance.entity.ZipCodeEntity;
import tech.aritra.springhexagonal.application.domain.Store;
import tech.aritra.springhexagonal.application.ports.out.UpdateStoreCountInZipCodePort;
import tech.aritra.springhexagonal.application.ports.out.StorePort;
@Component
public class StoreManagementDBAdapterInZipCode implements StorePort, UpdateStoreCountInZipCodePort {
private final StoreRepository storeRepository;
private final ZipCodeRepository zipCodeRepository;
@Autowired
public StoreManagementDBAdapterInZipCode(StoreRepository storeRepository, ZipCodeRepository zipCodeRepository) {
this.storeRepository = storeRepository;
this.zipCodeRepository = zipCodeRepository;
}
@Override
public Mono<Store> addStore(Store store) {
return storeRepository.save(StoreEntity.fromDomain(store)).map(StoreEntity::toDomain);
}
@Override
public void incrementStoreCount(String zipCode) {
zipCodeRepository.findByZipCode(zipCode)
.flatMap(zipCodeEntity -> zipCodeRepository.save(new ZipCodeEntity(zipCodeEntity.getId(), zipCodeEntity.getZipCode(), zipCodeEntity.getStoreCount() + 1)))
.subscribe();
}
}

View File

@@ -0,0 +1,9 @@
package tech.aritra.springhexagonal.adapters.persistance;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository;
import tech.aritra.springhexagonal.adapters.persistance.entity.StoreEntity;
@Repository
public interface StoreRepository extends ReactiveMongoRepository<StoreEntity, String> {
}

View File

@@ -0,0 +1,11 @@
package tech.aritra.springhexagonal.adapters.persistance;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Mono;
import tech.aritra.springhexagonal.adapters.persistance.entity.ZipCodeEntity;
@Repository
public interface ZipCodeRepository extends ReactiveMongoRepository<ZipCodeEntity, String> {
Mono<ZipCodeEntity> findByZipCode(String zipCode);
}

View File

@@ -0,0 +1,11 @@
package tech.aritra.springhexagonal.adapters.persistance.entity;
import lombok.Value;
import java.util.List;
@Value
public class GeoJSONPoint {
String type = "Point";
List<Double> coordinates;
}

View File

@@ -0,0 +1,34 @@
package tech.aritra.springhexagonal.adapters.persistance.entity;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import tech.aritra.springhexagonal.application.domain.Location;
import tech.aritra.springhexagonal.application.domain.Store;
import java.util.List;
@Document(collection = "stores")
public class StoreEntity {
@Id
private String id;
private final String name;
private final String zipCode;
private final GeoJSONPoint geoJSONPoint;
public StoreEntity(String name, String zipCode, GeoJSONPoint geoJSONPoint) {
this.name = name;
this.zipCode = zipCode;
this.geoJSONPoint = geoJSONPoint;
}
public static StoreEntity fromDomain(Store store) {
return new StoreEntity(
store.getName(),
store.getLocation().getZipCode(),
new GeoJSONPoint(List.of(store.getLocation().getLongitude(), store.getLocation().getLatitude())));
}
public Store toDomain() {
return new Store(id, name, new Location(zipCode, geoJSONPoint.getCoordinates().get(1), geoJSONPoint.getCoordinates().get(0)));
}
}

View File

@@ -0,0 +1,14 @@
package tech.aritra.springhexagonal.adapters.persistance.entity;
import lombok.Value;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Value
@Document(collection = "zip-codes")
public class ZipCodeEntity {
@Id
String id;
String zipCode;
int storeCount;
}

View File

@@ -0,0 +1,28 @@
package tech.aritra.springhexagonal.adapters.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import tech.aritra.springhexagonal.adapters.web.presenter.StorePresenter;
import tech.aritra.springhexagonal.application.ports.in.StoreManagementUseCase;
@Component
public class StoreHandler {
private final StoreManagementUseCase useCase;
@Autowired
public StoreHandler(StoreManagementUseCase useCase) {
this.useCase = useCase;
}
public Mono<ServerResponse> addStore(ServerRequest request) {
return request.bodyToMono(StorePresenter.class)
.flatMap(storePresenter -> ServerResponse.status(HttpStatus.CREATED)
.contentType(MediaType.APPLICATION_JSON)
.body(useCase.addStore(storePresenter.toDomain()).map(StorePresenter::fromDomain), StorePresenter.class));
}
}

View File

@@ -0,0 +1,19 @@
package tech.aritra.springhexagonal.adapters.web;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
@Configuration
public class StoreRouter {
@Bean
public RouterFunction<ServerResponse> routes(StoreHandler handler) {
return route(POST("/stores").and(accept(MediaType.APPLICATION_JSON)), handler::addStore);
}
}

View File

@@ -0,0 +1,30 @@
package tech.aritra.springhexagonal.adapters.web.presenter;
import lombok.AllArgsConstructor;
import lombok.Data;
import tech.aritra.springhexagonal.application.domain.Location;
import tech.aritra.springhexagonal.application.domain.Store;
@Data
@AllArgsConstructor
public class StorePresenter {
private String id;
private final String name;
private final String zipCode;
private final Double latitude;
private final Double longitude;
public Store toDomain() {
return new Store(name, new Location(zipCode, latitude, longitude));
}
public static StorePresenter fromDomain(Store store) {
return new StorePresenter(
store.getId(),
store.getName(),
store.getLocation().getZipCode(),
store.getLocation().getLatitude(),
store.getLocation().getLongitude());
}
}

View File

@@ -0,0 +1,35 @@
package tech.aritra.springhexagonal.application;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Mono;
import tech.aritra.springhexagonal.application.domain.Store;
import tech.aritra.springhexagonal.application.ports.in.StoreManagementUseCase;
import tech.aritra.springhexagonal.application.ports.out.UpdateStoreCountInZipCodePort;
import tech.aritra.springhexagonal.application.ports.out.StorePort;
import javax.naming.directory.InvalidAttributesException;
@Service
public class StoreService implements StoreManagementUseCase {
private final StorePort storePort;
private final UpdateStoreCountInZipCodePort updateStoreCountInZipCodePort;
@Autowired
public StoreService(StorePort storePort, UpdateStoreCountInZipCodePort updateStoreCountInZipCodePort) {
this.storePort = storePort;
this.updateStoreCountInZipCodePort = updateStoreCountInZipCodePort;
}
@Override
@Transactional
public Mono<Store> addStore(Store store) {
if (store.isValid()) {
final Mono<Store> result = storePort.addStore(store);
updateStoreCountInZipCodePort.incrementStoreCount(store.getLocation().getZipCode());
return result;
}
return Mono.error(InvalidAttributesException::new);
}
}

View File

@@ -0,0 +1,30 @@
package tech.aritra.springhexagonal.application.domain;
public class Location {
private final String zipCode;
private final Double latitude;
private final Double longitude;
public Location(String zipCode, Double latitude, Double longitude) {
this.zipCode = zipCode;
this.latitude = latitude;
this.longitude = longitude;
}
boolean isValid() {
// Some validation logic
return true;
}
public String getZipCode() {
return zipCode;
}
public Double getLatitude() {
return latitude;
}
public Double getLongitude() {
return longitude;
}
}

View File

@@ -0,0 +1,36 @@
package tech.aritra.springhexagonal.application.domain;
import org.springframework.util.StringUtils;
public class Store {
private String id;
private final String name;
private final Location location;
public Store(String name, Location location) {
this.name = name;
this.location = location;
}
public Store(String id, String name, Location location) {
this.id = id;
this.name = name;
this.location = location;
}
public boolean isValid() {
return StringUtils.hasText(name) && location.isValid();
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public Location getLocation() {
return location;
}
}

View File

@@ -0,0 +1,9 @@
package tech.aritra.springhexagonal.application.ports.in;
import reactor.core.publisher.Mono;
import tech.aritra.springhexagonal.application.domain.Store;
public interface StoreManagementUseCase {
Mono<Store> addStore(Store store);
}

View File

@@ -0,0 +1,8 @@
package tech.aritra.springhexagonal.application.ports.out;
import reactor.core.publisher.Mono;
import tech.aritra.springhexagonal.application.domain.Store;
public interface StorePort {
Mono<Store> addStore(Store store);
}

View File

@@ -0,0 +1,5 @@
package tech.aritra.springhexagonal.application.ports.out;
public interface UpdateStoreCountInZipCodePort {
void incrementStoreCount(String zipCode);
}

View File

@@ -1 +0,0 @@

View File

@@ -0,0 +1,7 @@
spring:
data:
mongodb:
uri: mongodb://localhost:27017/storesDB
server:
port: 9090