ADD: Basic solution
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package tech.aritra.springhexagonal.application.ports.out;
|
||||
|
||||
public interface UpdateStoreCountInZipCodePort {
|
||||
void incrementStoreCount(String zipCode);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
7
src/main/resources/application.yml
Normal file
7
src/main/resources/application.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
spring:
|
||||
data:
|
||||
mongodb:
|
||||
uri: mongodb://localhost:27017/storesDB
|
||||
|
||||
server:
|
||||
port: 9090
|
||||
Reference in New Issue
Block a user