first working state.

Customer can be registered and be written into the db
This commit is contained in:
Peter Straßer
2021-04-11 14:09:18 +02:00
parent 90eef66c5b
commit c1460d2ae2
26 changed files with 180 additions and 45 deletions

View File

@@ -46,5 +46,46 @@
<version>${org.lombok.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>14</source> <!-- depending on your project -->
<target>14</target> <!-- depending on your project -->
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.lombok.version}</version>
</path>
<!-- other annotation processors -->
</annotationProcessorPaths>
<compilerArgs>
<compilerArg>
-Amapstruct.defaultInjectionStrategy=constructor
</compilerArg>
<compilerArg>
-Amapstruct.defaultComponentModel=spring
</compilerArg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -3,12 +3,14 @@ package de.strasser.peter.hexagonal.persistence;
import de.strasser.peter.hexagonal.application.customer.domain.Customer;
import de.strasser.peter.hexagonal.application.customer.port.out.LoadCustomerAdapter;
import de.strasser.peter.hexagonal.application.customer.port.out.SaveCustomerAdapter;
import de.strasser.peter.hexagonal.persistence.mapper.CustomerMapper;
import de.strasser.peter.hexagonal.persistence.model.CustomerEntity;
import de.strasser.peter.hexagonal.persistence.repository.CustomerRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.math.BigInteger;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.Optional;
@@ -18,14 +20,16 @@ import java.util.Optional;
@RequiredArgsConstructor
public class CustomerDao implements SaveCustomerAdapter, LoadCustomerAdapter {
private final CustomerRepository customerRepository;
private final CustomerMapper customerMapper;
@Override
public void upsert(Customer customer) {
log.info("saving customer");
customerRepository.save(customerMapper.toDbEntity(customer));
}
@Override
public Customer findById(Integer id) {
public Customer findById(BigInteger id) {
Optional<CustomerEntity> byId = customerRepository.findById(id);
return Customer.createCustomer(id,
"max",

View File

@@ -3,13 +3,15 @@ package de.strasser.peter.hexagonal.persistence.mapper;
import de.strasser.peter.hexagonal.application.customer.domain.Address;
import de.strasser.peter.hexagonal.application.customer.domain.Customer;
import de.strasser.peter.hexagonal.persistence.model.CustomerEntity;
import org.mapstruct.Mapper;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Mapper
@Component
public class CustomerMapper {
Customer toDomain(CustomerEntity customerEntity) {
public Customer toDomain(CustomerEntity customerEntity) {
final HashMap<Address.AddressType, Address> addresses = createAddressMap(customerEntity);
return Customer.createCustomer(
customerEntity.getId(),
@@ -21,7 +23,35 @@ public class CustomerMapper {
}
CustomerEntity toDbEntity(Customer customer) {
public CustomerEntity toDbEntity(Customer customer) {
return new CustomerEntity(
customer.getId(),
customer.getName(),
customer.getHashedPassword(),
customer.getBirthday(),
customer.isActive(),
getAddressAttribute(customer, Address.AddressType.DEFAULT, Address::getStreet),
getAddressAttribute(customer, Address.AddressType.DEFAULT, Address::getHouseNumber),
getAddressAttribute(customer, Address.AddressType.DEFAULT, Address::getZipCode),
getAddressAttribute(customer, Address.AddressType.DEFAULT, Address::getCountry),
getAddressAttribute(customer, Address.AddressType.SHIPPING, Address::getStreet),
getAddressAttribute(customer, Address.AddressType.SHIPPING, Address::getHouseNumber),
getAddressAttribute(customer, Address.AddressType.SHIPPING, Address::getZipCode),
getAddressAttribute(customer, Address.AddressType.SHIPPING, Address::getCountry),
getAddressAttribute(customer, Address.AddressType.BILLING, Address::getStreet),
getAddressAttribute(customer, Address.AddressType.BILLING, Address::getHouseNumber),
getAddressAttribute(customer, Address.AddressType.BILLING, Address::getZipCode),
getAddressAttribute(customer, Address.AddressType.BILLING, Address::getCountry));
}
private <T> T getAddressAttribute(Customer customer, Address.AddressType type, Converter<Address, T> getter) {
final Map<Address.AddressType, Address> addresses = customer.getAddresses();
if (addresses != null) {
final Address address = addresses.get(type);
if (address != null) {
return getter.convert(address);
}
}
return null;
}

View File

@@ -5,6 +5,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import java.math.BigInteger;
import java.time.LocalDate;
@Data
@@ -12,25 +13,25 @@ import java.time.LocalDate;
@NoArgsConstructor
public class CustomerEntity {
@Id
private Integer id;
private BigInteger id;
private String name;
private String hashedPassword;
private LocalDate birthday;
private boolean active;
private String street;
private int houseNumber;
private int zipCode;
private Integer houseNumber;
private Integer zipCode;
private String country;
private String shippingStreet;
private int shippingHouseNumber;
private int shippingZipCode;
private Integer shippingHouseNumber;
private Integer shippingZipCode;
private String shippingCountry;
private String billingStreet;
private int billingHouseNumber;
private int billingZipCode;
private Integer billingHouseNumber;
private Integer billingZipCode;
private String billingCountry;
}

View File

@@ -4,6 +4,8 @@ import de.strasser.peter.hexagonal.persistence.model.CustomerEntity;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import java.math.BigInteger;
@Repository
public interface CustomerRepository extends MongoRepository<CustomerEntity, Integer> {
public interface CustomerRepository extends MongoRepository<CustomerEntity, BigInteger> {
}

View File

@@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigInteger;
import java.util.List;
@RestController
@@ -20,7 +21,7 @@ public class AddAddressController {
@PostMapping("/v1/address")
public void addAddress(@RequestParam Integer customerId, @RequestBody AddAddressRequest addAddressRequest) {
public void addAddress(@RequestParam BigInteger customerId, @RequestBody AddAddressRequest addAddressRequest) {
final List<AddAddressCommand> addAddressCmds = List.of(addAddressMapper.toCmd(addAddressRequest));
addAddressUseCase.addAddresses(customerId, addAddressCmds);
}

View File

@@ -0,0 +1,45 @@
package de.strasser.peter.hexagonal.webadapter.errors;
import de.strasser.peter.hexagonal.application.customer.exception.BusinessException;
import lombok.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import java.time.LocalDateTime;
@ControllerAdvice
public class ErrorHandling {
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ErrorResponse> constrainViolationException(HttpServletRequest req, ConstraintViolationException exc) {
return ErrorResponse.createErrorResp(req, HttpStatus.BAD_REQUEST, exc);
}
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> constrainViolationException(HttpServletRequest req, BusinessException exc) {
return ErrorResponse.createErrorResp(req, HttpStatus.BAD_REQUEST, exc);
}
@Value
public static class ErrorResponse {
String timestamp;
Integer status;
String error;
String message;
String path;
public static ResponseEntity<ErrorResponse> createErrorResp(HttpServletRequest req, HttpStatus code, Exception e) {
final ErrorResponse errResponse = new ErrorResponse(
LocalDateTime.now().toString(),
code.value(),
e.getClass().getName(),
e.getMessage(),
req.getContextPath());
return ResponseEntity.status(code).body(errResponse);
}
}
}

View File

@@ -3,8 +3,10 @@ package de.strasser.peter.hexagonal.webadapter.mapper;
import de.strasser.peter.hexagonal.application.customer.port.in.commands.RegisterCustomerCommand;
import de.strasser.peter.hexagonal.webadapter.model.RegisterCustomerRequest;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper
public interface RegisterCustomerRequestMapper {
@Mapping(source = "password", target = "clearPassword")
RegisterCustomerCommand toCmd(RegisterCustomerRequest registerCustomerRequest);
}

View File

@@ -1,10 +1,10 @@
package de.strasser.peter.hexagonal.webadapter.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Value;
@Value
@Data
@AllArgsConstructor
@NoArgsConstructor(force = true)
public class AddAddressRequest {

View File

@@ -1,16 +1,16 @@
package de.strasser.peter.hexagonal.webadapter.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Value;
import java.time.LocalDate;
@Value
@Data
@AllArgsConstructor
@NoArgsConstructor(force = true)
public class RegisterCustomerRequest {
String name;
LocalDate birthDay;
String clearPassword;
String password;
}

View File

@@ -13,7 +13,7 @@
<artifactId>application</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>application</name>
<description>Business logik for demo project</description>
<description>Business logic for demo project</description>
<properties>
<java.version>14</java.version>
<org.mapstruct.version>1.3.1.Final</org.mapstruct.version>

View File

@@ -6,8 +6,8 @@ import lombok.Value;
@Value
public class Address {
String street;
int houseNumber;
int zipCode;
Integer houseNumber;
Integer zipCode;
String country;

View File

@@ -4,6 +4,7 @@ import de.strasser.peter.hexagonal.application.customer.exception.DefaultAdressR
import de.strasser.peter.hexagonal.application.customer.exception.UserIsTooYoungExc;
import lombok.Getter;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.Period;
import java.util.HashMap;
@@ -11,7 +12,7 @@ import java.util.Map;
@Getter
public class Customer {
private final Integer id;
private final BigInteger id;
private String name;
private String hashedPassword;
private LocalDate birthday;
@@ -19,7 +20,7 @@ public class Customer {
private Map<Address.AddressType, Address> addresses;
private boolean active;
private Customer(Integer id, String name, String hashedPassword, LocalDate birthDate, Map<Address.AddressType, Address> addresses, boolean active) {
private Customer(BigInteger id, String name, String hashedPassword, LocalDate birthDate, Map<Address.AddressType, Address> addresses, boolean active) {
this.id = id;
this.active = active;
this.age = Period.between(birthDate, LocalDate.now()).getYears();
@@ -46,7 +47,7 @@ public class Customer {
}
public static Customer createCustomer(
Integer id,
BigInteger id,
String name,
String hashedPassword,
LocalDate birthDate,

View File

@@ -1,6 +1,6 @@
package de.strasser.peter.hexagonal.application.customer.exception;
public class AddressTypeDoesNotExistsExc extends IllegalArgumentException {
public class AddressTypeDoesNotExistsExc extends BusinessException {
public AddressTypeDoesNotExistsExc(String type) {
super(String.format("Adress of type '%s' does not exist!", type));
}

View File

@@ -0,0 +1,7 @@
package de.strasser.peter.hexagonal.application.customer.exception;
public abstract class BusinessException extends IllegalStateException {
public BusinessException(String error) {
super(error);
}
}

View File

@@ -1,6 +1,6 @@
package de.strasser.peter.hexagonal.application.customer.exception;
public class DefaultAdressRequiredToActivateExc extends IllegalStateException {
public class DefaultAdressRequiredToActivateExc extends BusinessException {
public DefaultAdressRequiredToActivateExc() {
super("Customer needs to have at least a default adress to be able to be activated!");

View File

@@ -2,7 +2,7 @@ package de.strasser.peter.hexagonal.application.customer.exception;
import java.text.MessageFormat;
public class UserIsTooYoungExc extends IllegalArgumentException {
public class UserIsTooYoungExc extends BusinessException {
public UserIsTooYoungExc(int age) {
super(MessageFormat.format("Customer is too young. Expected: > 18 yrs, Actual {0}", age));
}

View File

@@ -5,8 +5,9 @@ import de.strasser.peter.hexagonal.application.customer.port.in.commands.AddAddr
import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import java.math.BigInteger;
import java.util.List;
public interface AddAddressUseCase {
void addAddresses(@Min(0) Integer customerId, @Valid @NotEmpty List<AddAddressCommand> addresses);
void addAddresses(@Min(0) BigInteger customerId, @Valid @NotEmpty List<AddAddressCommand> addresses);
}

View File

@@ -1,13 +1,13 @@
package de.strasser.peter.hexagonal.application.customer.port.in.commands;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Value;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
@Value
@Data
@AllArgsConstructor
@NoArgsConstructor(force = true)
public class AddAddressCommand {

View File

@@ -3,14 +3,14 @@ package de.strasser.peter.hexagonal.application.customer.port.in.commands;
import de.strasser.peter.hexagonal.common.validators.SecurePassword;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Value;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Past;
import java.time.LocalDate;
@Value
@Data
@AllArgsConstructor
@NoArgsConstructor(force = true)
public class RegisterCustomerCommand {

View File

@@ -2,6 +2,8 @@ package de.strasser.peter.hexagonal.application.customer.port.out;
import de.strasser.peter.hexagonal.application.customer.domain.Customer;
import java.math.BigInteger;
public interface LoadCustomerAdapter {
Customer findById(Integer id);
Customer findById(BigInteger id);
}

View File

@@ -1,15 +1,15 @@
package de.strasser.peter.hexagonal.application.customer.port.out.commands;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Value;
@Value
@Data
@AllArgsConstructor
@NoArgsConstructor(force = true)
public class ValidateAddressCommand {
String street;
int houseNumber;
int zipCode;
Integer houseNumber;
Integer zipCode;
String country;
}

View File

@@ -14,6 +14,7 @@ import org.springframework.stereotype.Service;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -27,7 +28,7 @@ class AddressService implements AddAddressUseCase {
private final AddAddressMapper addAddressMapper;
@Override
public void addAddresses(@Min(0) Integer customerId, @Valid @NotEmpty List<AddAddressCommand> addAddressCmds) {
public void addAddresses(@Min(0) BigInteger customerId, @Valid @NotEmpty List<AddAddressCommand> addAddressCmds) {
final Customer customer = loadCustomerAdapter.findById(customerId);
final Map<Address.AddressType, Address> addresses = new HashMap<>();

View File

@@ -21,10 +21,6 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>

View File

@@ -21,7 +21,7 @@ public @interface SecurePassword {
"# a digit must occur at least once\n" +
"# a lower case letter must occur at least once\n" +
"# an upper case letter must occur at least once\n" +
"# a special character must occur at least once\n" +
"# a special character must occur at least once ( one of !@#$%^&*(),.?\":{}|<>) \n" +
"# no whitespace allowed in the entire string\n" +
"# anything, at least eight places though";
@@ -33,7 +33,7 @@ public @interface SecurePassword {
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
return s.matches("^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=])(?=\\S+$).{8,}$");
return s != null && s.matches("^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*(),.?\":{}|<>])(?=\\S+$).{8,}$");
}
}
}

View File

@@ -1 +1,2 @@
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false
logging.level.org.springframework.data.mongodb.core.MongoTemplate=DEBUG