Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 36 additions & 8 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@

<properties>
<java.version>11</java.version>
<org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
<guava.version>30.1.1-jre</guava.version>
<junit.jupiter.version>5.7.2</junit.jupiter.version>
<lombok.version>1.18.20</lombok.version>
<mysql.socket.factory.version>1.3.1</mysql.socket.factory.version>
<mysql.connector.version>8.0.24</mysql.connector.version>
<orika.core.version>1.5.4</orika.core.version>
<springfox.swagger2.version>2.5.0</springfox.swagger2.version>
<springfox.swagger.ui.version>2.5.0</springfox.swagger.ui.version>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice clean up here.

</properties>


Expand All @@ -32,7 +41,7 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1.1-jre</version>
<version>${guava.version}</version>
</dependency>

<!-- Jackson -->
Expand All @@ -53,35 +62,35 @@
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.7.2</version>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>

<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>

<!-- mySQL -->
<dependency>
<groupId>com.google.cloud.sql</groupId>
<artifactId>mysql-socket-factory-connector-j-8</artifactId>
<version>1.3.1</version>
<version>${mysql.socket.factory.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.24</version>
<version>${mysql.connector.version}</version>
</dependency>

<!-- orika -->
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.5.4</version>
<version>${orika.core.version}</version>
</dependency>

<!-- Spring Boot -->
Expand All @@ -107,12 +116,19 @@
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.5.0</version>
<version>${springfox.swagger2.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.5.0</version>
<version>${springfox.swagger.ui.version}</version>
</dependency>

<!-- Mapstruct -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>

Expand All @@ -124,6 +140,18 @@
<configuration>
<source>11</source>
<target>11</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/com/bravo/user/controller/PaymentController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.bravo.user.controller;

import com.bravo.user.annotation.SwaggerController;
import com.bravo.user.model.dto.PaymentDto;
import com.bravo.user.service.PaymentService;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@SwaggerController
@RequestMapping("/payments")
@RequiredArgsConstructor
@Slf4j
public class PaymentController {

private final PaymentService paymentService;

@GetMapping
@ResponseBody
public List<PaymentDto> findPaymentByUserId(@RequestParam String userId) {
log.info("Starting to search payment for user with id {}", userId);
return paymentService.findPaymentByUserId(userId);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.bravo.user.controller.advice;

import com.bravo.user.exception.PaymentNotFoundException;
import com.bravo.user.model.dto.ErrorDto;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ControllerAdvice;
Expand All @@ -11,6 +13,7 @@
import org.springframework.web.bind.annotation.ResponseStatus;

@ControllerAdvice
@Slf4j
public class ExceptionHandlerAdvice {

@ExceptionHandler(value = BindException.class)
Expand All @@ -31,4 +34,17 @@ public ErrorDto handleBindException(final BindException exception){
response.setStatusCode(400);
return response;
}

@ExceptionHandler(value = PaymentNotFoundException.class)
@ResponseBody
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public ErrorDto handlePaymentNotFoundException(final PaymentNotFoundException exception){
log.info("Payments not found, details: {}", exception.getMessage());
var errorDto = new ErrorDto();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

errorDto could be final

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, thanks

errorDto.setMessage(exception.getMessage());
errorDto.setStatusCode(HttpStatus.NOT_FOUND.value());

return errorDto;
}

}
10 changes: 3 additions & 7 deletions src/main/java/com/bravo/user/dao/model/Payment.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.bravo.user.dao.model;

import java.time.LocalDateTime;
import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Data;
import org.hibernate.annotations.GenericGenerator;

@Entity
@Data
Expand All @@ -15,6 +15,7 @@ public class Payment {

@Id
@Column(name = "id")
@GenericGenerator(name = "uuid", strategy = "uuid4")
private String id;

@Column(name = "user_id", nullable = false)
Expand All @@ -30,11 +31,6 @@ public class Payment {
private Integer expiryYear;

@Column(name = "updated", nullable = false)
private LocalDateTime updated;
private LocalDateTime updated = LocalDateTime.now();

public Payment(){
super();
this.id = UUID.randomUUID().toString();
this.updated = LocalDateTime.now();
}
}
23 changes: 23 additions & 0 deletions src/main/java/com/bravo/user/dao/model/mapper/PaymentMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.bravo.user.dao.model.mapper;

import com.bravo.user.dao.model.Payment;
import com.bravo.user.model.dto.PaymentDto;
import java.util.List;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;

@Mapper(componentModel = "spring")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what componentModel = "spring" means?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wheelerswebservices it means construct mapper as spring bean. Because mapper has different construction types, using factory, spring beans etc.

public interface PaymentMapper {

@Mapping(source = "cardNumber", target = "cardNumber", qualifiedByName = "lastFourCardDigits")
PaymentDto toPaymentDto(Payment payment);

@Mapping(source = "cardNumber", target = "cardNumber", qualifiedByName = "lastFourCardDigits")
List<PaymentDto> toPaymentDtoList(List<Payment> payments);

@Named("lastFourCardDigits")
static String lastFourCardDigits(String cardNumber) {
return cardNumber.replaceAll("\\w(?=\\w{4})", "*");
}
}
13 changes: 0 additions & 13 deletions src/main/java/com/bravo/user/dao/model/mapper/ResourceMapper.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package com.bravo.user.dao.model.mapper;

import com.bravo.user.dao.model.Address;
import com.bravo.user.dao.model.Payment;
import com.bravo.user.dao.model.Profile;
import com.bravo.user.dao.model.User;
import com.bravo.user.model.dto.AddressDto;
import com.bravo.user.model.dto.PaymentDto;
import com.bravo.user.model.dto.ProfileDto;
import com.bravo.user.model.dto.UserReadDto;
import java.util.Collection;
Expand Down Expand Up @@ -41,17 +39,6 @@ public AddressDto convertAddress(final Address address){
return dto;
}

public <T extends Collection<Payment>> List<PaymentDto> convertPayments(final T payments){
return payments.stream().map(this::convertPayment).collect(Collectors.toList());
}

public PaymentDto convertPayment(final Payment payment){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate on why the new PaymentMapper class is superior to these methods within the existing ResourceMapper class?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wheelerswebservices because mapperFacade, which is used here is not the flexible one from SOLID principles perpective.
First of all, if you would have 10 dto's which you need to map from the entity its not a good idea to create 20 methods inside ResourseMapper class.
Second, is that you need to implement all of the extra mapping, or when field names are different. In the Mapstruct its simple, you only need an interface, and specify everything only using annotations. Its very useful and simple to understand. Its flexible when you need some extra mappings, for example as was for card mask. So everything for this mapping is located only in 1 Java interface.
Thats why mapstruct is so popular today.
You could read all the details below: https://www.baeldung.com/java-performance-mapping-frameworks

final String cardNumber = payment.getCardNumber();
final PaymentDto dto = mapperFacade.map(payment, PaymentDto.class);
dto.setCardNumberLast4(cardNumber.substring(cardNumber.length() - 5));
return dto;
}

public ProfileDto convertProfile(final Profile profile){
return mapperFacade.map(profile, ProfileDto.class);
}
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/bravo/user/dao/repository/PaymentRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.bravo.user.dao.repository;

import com.bravo.user.dao.model.Payment;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface PaymentRepository extends JpaRepository<Payment, String> {
List<Payment> findByUserId(String userId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.bravo.user.exception;

import java.text.MessageFormat;

public class PaymentNotFoundException extends RuntimeException {

private static final String MESSAGE = "Payment not found for user with id {0}";

public PaymentNotFoundException(String userId) {
super(MessageFormat.format(MESSAGE, userId));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Why did you use MessageFormat.format instead of String.format?
  2. Do you think this MESSAGE variable is really necessary as it's only used in 1 place?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wheelerswebservices regarding first: I don't think this is really matters
second: this exception is only related to Payment not found flow, so there is not reason to extract this message somewhere else.

}
}
4 changes: 4 additions & 0 deletions src/main/java/com/bravo/user/model/dto/ErrorDto.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package com.bravo.user.model.dto;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import java.util.Set;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@JsonInclude(Include.NON_NULL)
public class ErrorDto {

private Set<String> errors;
private Class<?> exception;
private String message;
private Integer statusCode;

}
9 changes: 7 additions & 2 deletions src/main/java/com/bravo/user/model/dto/PaymentDto.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package com.bravo.user.model.dto;

import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PaymentDto {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why annotate with Builder, AllArgsConstructor, and NoArgsConstructor if they are not all being used?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder used to simplify construction for testing. Since we use Builder, NoArgs and AllArgs constrcustors is required for MapStruct.


private String id;
private String cardNumberLast4;
private String cardNumber;
private Integer expiryMonth;
private Integer expiryYear;
private LocalDateTime updated;
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/com/bravo/user/service/PaymentService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.bravo.user.service;

import com.bravo.user.dao.model.mapper.PaymentMapper;
import com.bravo.user.dao.repository.PaymentRepository;
import com.bravo.user.exception.PaymentNotFoundException;
import com.bravo.user.model.dto.PaymentDto;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@Slf4j
@RequiredArgsConstructor
public class PaymentService {

private final PaymentRepository paymentRepository;
private final PaymentMapper paymentMapper;

public List<PaymentDto> findPaymentByUserId(String userId) {
var payments = paymentRepository.findByUserId(userId);

if(payments.isEmpty()) {
throw new PaymentNotFoundException(userId);
}

return paymentMapper.toPaymentDtoList(payments);
}

}
7 changes: 7 additions & 0 deletions src/main/resources/data.sql
Original file line number Diff line number Diff line change
Expand Up @@ -648,3 +648,10 @@ insert into address (id, user_id, line1, line2, city, state, zip) values
('42f33d30-f3f8-4743-a94e-4db11fdb747d', '008a4215-0b1d-445e-b655-a964039cbb5a', '412 Maple St', null, 'Dowagiac', 'Michigan', '49047'),
('579872ec-46f8-46b5-b809-d0724d965f0e', '00963d9b-f884-485e-9455-fcf30c6ac379', '237 Mountain Ter', 'Apt 10', 'Odenville', 'Alabama', '35120'),
('95a983d0-ba0e-4f30-afb6-667d4724b253', '00963d9b-f884-485e-9455-fcf30c6ac379', '107 Annettes Ct', null, 'Aydlett', 'North Carolina', '27916');

insert into payment (id, user_id, card_number, expiry_month, expiry_year) values
('45cb8ef20-5e39-472e-a967-06dee934b0f3', '008a4215-0b1d-445e-b655-a964039cbb5a', '288925317311943', '02', '2026'),
('579872ec-46f8-46b5-b809-d0724d965f0e', '008a4215-0b1d-445e-b655-a964039cbb5a', '377925317620670', '01', '2031'),
('95a983d0-ba0e-4f30-afb6-667d4724b253', '008a4215-0b1d-445e-b655-a964039cbb5a', '377925317621133', '02', '2021'),
('ddabb1fa-0c94-451e-aa76-84c14a24c578', '00963d9b-f884-485e-9455-fcf30c6ac379', '374141394130819', '05', '2023'),
('fa57433d-b7ed-4028-99d9-ce1919362de2', '00963d9b-f884-485e-9455-fcf30c6ac379', '372120757513243', '03', '2025');
25 changes: 25 additions & 0 deletions src/test/java/com/bravo/user/TestUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.bravo.user;

import com.bravo.user.config.AppConfig;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.nio.file.Files;
import java.nio.file.Path;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;

@UtilityClass
public class TestUtils {
private static final ObjectMapper OBJECT_MAPPER = new AppConfig().objectMapperBuilder().build();

@SneakyThrows
public String readFromFile(String file) {
var url = TestUtils.class.getResource(file);
return Files.readString(Path.of(url.getPath()));
}

@SneakyThrows
public static <T> T deserializeFromJson(String json, TypeReference<T> clazz) {
return OBJECT_MAPPER.readValue(json, clazz);
}
}
Loading