지금까지 배웠던 기술들을 다듬을겸 주문&선물 프로젝트를 진행합니다
지금까지 공부했던 기술들을 다듬는다.
유용한 기능을 개발하는 것 보다는 기존에 했던 프로젝트를 새롭게 알게된 기술들로 재구현 하는데에 초점을 둔다.
기획에서 설계부터 실제 개발까지 진행하는것도 좋지만, 여기에서는 기획력이나 설계하는 능력을 기르기보다는 지금까지 배운 기술들을 정리하는데에 집중한다.
단순히 새로운 기술로 전환하기보다는 요구사항만을 보면서 분석하고 처음부터 개발한다
기존 프로젝트: https://github.com/sinkyoungdeok/msa
기존 프로젝트는 패스트캠퍼스의 The Red강의를 들으면서 클론코딩을 진행하였다.
아키텍처: msa, ddd
언어: java
프레임워크: spring boot, spring mvc, spring data jpa
라이브러리: jpa, retrofit2, mapstruct
빌드툴: gradle
메시지 브로커: aws sqs
컨테이너툴: docker
데이터베이스: mysql
아키텍처: msa, ddd
언어: kotlin
프레임워크: spring boot, spring webflux, spring data mongodb reactive, spring data redis,spring security jwt
라이브러리: spring kafka, retrofit2, mapstruct
빌드툴: gradle
메시지 브로커: kafka
컨테이너툴: docker
데이터베이스: mongodb, redis
주문 프로젝트: 2022.01.20 ~ 2022.01.31.
선물 프로젝트: 2022.01.31 ~ 2022.02.01.
로그인 기능 추가: 2022.02.27 ~ ing
지금 개발중이므로 상세 사용법은 완성후에 작성 예정.
로그인 기능 없이 프로젝트를 실행 해보고 싶다면 다음 버전을 사용 링크 클릭
docker-compose -f docker/docker-compose.yml up -d
전체 Flow 간략 버전
선물하기 서비스와 주문 서비스간의 의존관게
전체 Flow
실제 Flow
선물 구매 시
선물 수락 시
선물 거절 시
나중에 Kafka를 통해서 얻을 수 있는 설계
@Repository
interface PartnerRepository : ReactiveMongoRepository<Partner, String>
implementation("org.springframework.boot:spring-boot-starter-data-mongodb")
Description:
Parameter 0 of constructor in msa.order.infrastructure.partner.PartnerStoreImpl required a bean of type 'msa.order.infrastructure.partner.PartnerRepository' that could not be found.
Action:
Consider defining a bean of type 'msa.order.infrastructure.partner.PartnerRepository' in your configuration.
Process finished with exit code 1
implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive")
spring:
data:
mongodb:
host: localhost
port: 27017
authentication-database: admin
username: root
password: 1234
database: order
org.springframework.data.mongodb.UncategorizedMongoDbException: Exception authenticating MongoCredential{mechanism=SCRAM-SHA-256, userName='root', source='admin', password=<hidden>, mechanismProperties=<hidden>}; nested exception is com.mongodb.MongoSecurityException: Exception authenticating MongoCredential{mechanism=SCRAM-SHA-256, userName='root', source='admin', password=<hidden>, mechanismProperties=<hidden>}
at org.springframework.data.mongodb.core.MongoExceptionTranslator.translateExceptionIfPossible(MongoExceptionTranslator.java:140) ~[spring-data-mongodb-3.3.0.jar:3.3.0]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
*__checkpoint ⇢ Handler org.springframework.web.reactive.function.server.RouterFunctionDsl$POST$2@102b2dbd [DispatcherHandler]
*__checkpoint ⇢ HTTP POST "/api/v1/partners" [ExceptionHandlingWebHandler]
Original Stack Trace:
...
spring:
data:
mongodb:
uri: mongodb://root:1234@localhost/order?authSource=admin
@PostMapping
fun registerPartner(
@Valid @RequestBody request: PartnerDto.RegisterRequest
): Mono<CommonResponse<PartnerDto.RegisterResponse>> {
var command: Mono<PartnerCommand.RegisterPartner> = Mono.just(request.toCommand())
var partnerInfo = partnerFacade.registerPartner(command)
var response = partnerInfo.map { PartnerDto.RegisterResponse(it) }
return response.map { CommonResponse(it) }
}
class PartnerDto {
class RegisterRequest(
@field:NotEmpty(message = "partnerName 은 필수값 입니다")
var partnerName: String? = null,
@field:NotEmpty(message = "businessNo 는 필수값 입니다")
var businessNo: String? = null,
@field:Email(message = "email 형식에 맞추어야 합니다")
@field:NotEmpty(message = "email 은 필수값 입니다")
var email: String? = null
)
}
@RestControllerAdvice
class CommonControllerAdvice {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = [MethodArgumentNotValidException::class])
fun methodArgumentNotValidException(e: MethodArgumentNotValidException): Mono<CommonResponse<String>> {
// ...
return Mono.just(errorResponse)
}
}
http 요청
POST http://localhost:8080/api/v1/partners
Content-Type: application/json
{
"partnerName": "",
"businessNo": "1234123456",
"email": "greg.shiny8"
}
MethodArgumentNotValidException::class
가 아닌 WebExchangeBindException::class
를 감지할 수 있도록 변경하였다.@RestControllerAdvice
class CommonControllerAdvice {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = [WebExchangeBindException::class])
fun methodArgumentNotValidException(e: WebExchangeBindException): Mono<CommonResponse<String>> {
// ...
return Mono.just(errorResponse)
}
}
implementation("org.mapstruct:mapstruct:1.4.2.Final")
annotationProcessor("org.mapstruct:mapstruct-processor:1.4.2.Final")
annotationProcessor(
"org.projectlombok:lombok",
"org.projectlombok:lombok-mapstruct-binding:0.1.0"
)
@Mapper(
componentModel = "spring",
unmappedTargetPolicy = ReportingPolicy.ERROR
)
interface PartnerDtoMapper {
fun of(request: PartnerDto.RegisterRequest): PartnerCommand.RegisterPartner
}
@RestController
@RequestMapping("/api/v1/partners")
class PartnerApiController(val partnerFacade: PartnerFacade, val partnerDtoMapper: PartnerDtoMapper) {
//...
}
val partnerDtoMapper: PartnerDtoMapper
에서 빈을 인식 못하는 상황 발생PartnerDtoMapper
의 구현체를 mapstruct에서 만들어주어야 하는데, 이것 또한 안되었었다.Description:
Parameter 1 of constructor in msa.order.interfaces.partner.PartnerApiController required a bean of type 'msa.order.interfaces.partner.PartnerDtoMapper' that could not be found.
Action:
Consider defining a bean of type 'msa.order.interfaces.partner.PartnerDtoMapper' in your configuration.
Process finished with exit code 1
plugins {
...
kotlin("kapt") version "1.3.72" // 추가
}
...
dependencies {
...
// MapStruct
implementation("org.mapstruct:mapstruct:1.4.2.Final")
kapt("org.mapstruct:mapstruct-processor:1.4.2.Final")
implementation("org.projectlombok:lombok-mapstruct-binding:0.1.0")
annotationProcessor("org.mapstruct:mapstruct-processor:1.4.2.Final")
annotationProcessor(
"org.projectlombok:lombok",
"org.projectlombok:lombok-mapstruct-binding:0.1.0"
)
...
}
@Repository
interface PartnerRepository : ReactiveMongoRepository<Partner, String> {
fun findByPartnerToken(partnerToken: Mono<String>): Mono<Partner>
}
@Component
class PartnerReaderImpl(
val partnerRepository: PartnerRepository
) : PartnerReader {
override fun getPartner(partnerToken: Mono<String>): Mono<Partner> {
return partnerRepository.findByPartnerToken(partnerToken)
}
}
@Repository
interface PartnerRepository : ReactiveMongoRepository<Partner, String> {
fun findByPartnerToken(partnerToken: String): Mono<Partner>
}
@Component
class PartnerReaderImpl(
val partnerRepository: PartnerRepository
) : PartnerReader {
override fun getPartner(partnerToken: Mono<String>): Mono<Partner> {
return partnerToken.flatMap { partnerRepository.findByPartnerToken(it) }
}
}
@PostMapping
fun registerItem(
@RequestBody @Valid request: Mono<ItemDto.RegisterItemRequest>
): Mono<CommonResponse<ItemDto.RegisterResponse>> {
var partnerToken = request.map { it.partnerToken ?: "" }
var itemCommand = request.map { itemDtoMapper.of(it) }
var itemInfo = itemFacade.registerItem(itemCommand, partnerToken)
var response = itemInfo.map { itemDtoMapper.of(it) }
return response.map { CommonResponse(it) }
}
override fun registerItem(
command: Mono<ItemCommand.RegisterItemRequest>,
partnerToken: Mono<String>
): Mono<ItemInfo.Token> {
return partnerReader.getPartner(partnerToken)
.flatMap { p ->
val map: Mono<Item> = command.map { c ->
p.id?.let { c.toEntity(it) }
}
map
}.map {
ItemInfo.Token(it.itemName)
}
@Component
class GiftKafkaMessageListener(
val giftFacade: GiftFacade
) {
@KafkaListener(topics = arrayOf("pay-complete"))
suspend fun payComplete(orderToken: String) {
giftFacade.completePayment(orderToken)
}
}
cd order && cd order-and-gift-idl && git pull origin main && ./gradlew generateProto
cd gift && cd order-and-gift-idl && git pull origin main && ./gradlew generateProto