프레진테이션 레이어 | 사용자 인터페이스 담당 |
애플리케이션 레이어 | 애플리케이션의 전체 흐름을 유지하고 조정. RESTful 웹 서비스, 비동기 API, gRPC API, GraphQL API는 이 레이어의 일부 |
도메인 레이어 | 비즈니스 로직과 도메인 정보 포함. 서비스와 리포지토리로 구성 |
인프라 레이어 | 다른 모든 레이어에 대한 지원 담당 스프링부트가 인프라 레이어로 작동. 데이터베이스, 메시지 브로커 등과 같은 외부 및 내부 시스템과의 통신 및 상호작용 담당. |
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.flywaydb:flyway-core'
runtimeOnly 'com.h2database:h2'
# 데이터소스 설정
spring.datasource.name=ecomm
spring.datasource.url=jdbc:h2:mem:ecomm;DB_CLOSE_DELAY=-1;IGNORECASE=TRUE;DATABASE_TO_UPPER=false
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# H2 데이터베이스 설정
spring.h2.console.enabled=true
spring.h2.console.settings.web-allow-others=false
# JPA 설정
spring.jpa.properties.hibernate.default_schema=ecomm
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.show-sql=true
spring.jpa.format_sql=true
spring.jpa.generate-ddl=false
spring.jpa.hibernate.ddl-auto=none
# Flyway 설정
spring.flyway.url=jdbc:h2:mem:ecomm
spring.flyway.schemas=ecomm
spring.flyway.user=sa
spring.flyway.password=
Modern-API-Development-with-Spring-6-and-Spring-Boot-3/Chapter04/src/main/resources/db/migration/V1.0.0__Init.sql at main · Pac
Modern API Development with Spring 6 and Spring Boot 3, Published by Packt - PacktPublishing/Modern-API-Development-with-Spring-6-and-Spring-Boot-3
github.com
@Entity
@Table(name = "cart")
public class CartEntity {
@Id
@GeneratedValue
@Column(name = "ID", updatable = false, nullable = false)
private UUID id;
@OneToOne
@JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private UserEntity user;
@ManyToMany(
cascade = CascadeType.ALL
)
@JoinTable(
name = "CART_ITEM",
joinColumns = @JoinColumn(name = "CART_ID"),
inverseJoinColumns = @JoinColumn(name = "ITEM_ID")
)
private List<ItemEntity> items = new ArrayList<>();
}
@Entity
@Table(name = "\"user\"")
public class UserEntity {
@Id
@GeneratedValue
@Column(name = "ID", updatable = false, nullable = false)
private UUID id;
@NotNull(message = "User name is required.")
@Basic(optional = false)
@Column(name = "USERNAME")
private String username;
@Column(name = "PASSWORD")
private String password;
@Column(name = "FIRST_NAME")
private String firstName;
@Column(name = "LAST_NAME")
private String lastName;
@Column(name = "EMAIL")
private String email;
@Column(name = "PHONE")
private String phone;
@Column(name = "USER_STATUS")
private String userStatus;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinTable(
name = "USER_ADDRESS",
joinColumns = @JoinColumn(name = "USER_ID"),
inverseJoinColumns = @JoinColumn(name = "ADDRESS_ID")
)
private List<AddressEntity> addresses = new ArrayList<>();
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY, orphanRemoval = true)
private List<CardEntity> cards;
@OneToOne(mappedBy = "user", fetch = FetchType.LAZY, orphanRemoval = true)
private CartEntity cart;
@OneToMany(mappedBy = "userEntity", fetch = FetchType.LAZY, orphanRemoval = true)
private List<OrderEntity> orders;
}
public interface CartRepository extends CrudRepository<CartEntity, UUID> {
@Query("select c from CartEntity c join c.user u where u.id = :customerId")
Optional<CartEntity> findByCustomerId(@Param("customerId") UUID customerId);
}
public interface CartService {
List<Item> addCartItemsByCustomerId(String customerId, @Valid Item item);
}
@Service
public class CartServiceImpl implements CartService {
private final CartRepository repository;
private final UserRepository userRepo;
private final ItemService itemService;
public CartServiceImpl(CartRepository repository, UserRepository userRepo,
ItemService itemService) {
this.repository = repository;
this.userRepo = userRepo;
this.itemService = itemService;
}
@Override
public List<Item> addCartItemsByCustomerId(String customerId, @Valid Item item) {
CartEntity entity = getCartByCustomerId(customerId);
long count = entity.getItems().stream()
.filter(i -> i.getProduct().getId().equals(UUID.fromString(item.getId()))).count();
if (count > 0) {
throw new GenericAlreadyExistsException(
String.format("Item with Id (%s) already exists. You can update it.", item.getId()));
}
entity.getItems().add(itemService.toEntity(item));
return itemService.toModelList(repository.save(entity).getItems());
}
}
public class Cart extends RepresentationModel<Cart> implements Serializable {
// ...
}
{
...
"hateoas": true,
...
}
@Component
public class CartRepresentationModelAssembler extends
RepresentationModelAssemblerSupport<CartEntity, Cart> {
private final ItemService itemService;
public CartRepresentationModelAssembler(ItemService itemService) {
super(CartsController.class, Cart.class);
this.itemService = itemService;
}
@Override
public Cart toModel(CartEntity entity) {
String uid = Objects.nonNull(entity.getUser()) ? entity.getUser().getId().toString() : null;
String cid = Objects.nonNull(entity.getId()) ? entity.getId().toString() : null;
Cart resource = new Cart();
BeanUtils.copyProperties(entity, resource);
resource.id(cid).customerId(uid).items(itemService.toModelList(entity.getItems()));
resource.add(linkTo(methodOn(CartsController.class).getCartByCustomerId(uid)).withSelfRel());
resource.add(linkTo(methodOn(CartsController.class).getCartItemsByCustomerId(uid))
.withRel("cart-items"));
return resource;
}
public List<Cart> toListModel(Iterable<CartEntity> entities) {
if (Objects.isNull(entities)) {
return List.of();
}
return StreamSupport.stream(entities.spliterator(), false).map(this::toModel)
.collect(toList());
}
}
@Override
public Order process(Order model){
model.add(Link.of("/payments/{orderId}").withRel(
LinkRelation.of("payments")).expand(model.getOrderId()));
return model;
}
@RestController
public class CartsController implements CartApi {
// ...
private final CartRepresentationModelAssembler assembler;
@Override
public ResponseEntity<Cart> getCartByCustomerId(String customerId) {
return ok(assembler.toModel(service.getCartByCustomerId(customerId)));
}
ETag
If-None-Match 헤더를 사용해 요청하기
ShallowEtagHeaderFilter를 사용해 ETag 구현
CacheControl 클래스를 사용해 ETag 구현
Return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(5, TimeUnit.DAYS))
.eTag(product.getModifiedDateInEpoch())
.body(product);
Modern-API-Development-with-Spring-6-and-Spring-Boot-3/Chapter04/Chapter04-API-Collection.har at main · PacktPublishing/Modern-
Modern API Development with Spring 6 and Spring Boot 3, Published by Packt - PacktPublishing/Modern-API-Development-with-Spring-6-and-Spring-Boot-3
github.com
소라브 샤르마, 『스프링 6와 스프링 부트 3로 배우는 모던 API 개발』 5장 (0) | 2025.02.03 |
---|---|
소라브 샤르마, 『스프링 6와 스프링 부트 3로 배우는 모던 API 개발』 3장 (0) | 2025.01.13 |
소라브 샤르마, 『스프링 6와 스프링 부트 3로 배우는 모던 API 개발』 2장 (3) | 2025.01.09 |
소라브 샤르마, 『스프링 6와 스프링 부트 3로 배우는 모던 API 개발』 1장 (0) | 2024.12.24 |
[필독! 개발자 온보딩 가이드] 시맨틱 버저닝 (0) | 2024.02.01 |