#38 hexagonal: banking service - register bank account
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
package org.example.banking.adapter.in.web;
|
||||
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.example.banking.application.port.in.RegisterBankAccountCommand;
|
||||
import org.example.banking.application.port.in.RegisterBankAccountUseCase;
|
||||
import org.example.banking.domain.RegisteredBankAccount;
|
||||
import org.example.common.WebAdapter;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@WebAdapter
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
public class RegisterBankAccountController {
|
||||
|
||||
private final RegisterBankAccountUseCase registerBankAccountUseCase;
|
||||
|
||||
@PostMapping("/banking/account/register")
|
||||
RegisteredBankAccount registeredBankAccount(@RequestBody RegisterBankAccountRequest request) {
|
||||
|
||||
RegisterBankAccountCommand command = RegisterBankAccountCommand.builder()
|
||||
.membershipId(request.getMembershipId())
|
||||
.bankName(request.getBankName())
|
||||
.bankAccountNumber(request.getBankAccountNumber())
|
||||
.isValid(true)
|
||||
.build();
|
||||
|
||||
RegisteredBankAccount registeredBankAccount = registerBankAccountUseCase.registerBankAccount(command);
|
||||
|
||||
if (registeredBankAccount == null) {
|
||||
|
||||
// TODO Error Handling
|
||||
return null;
|
||||
}
|
||||
|
||||
return registeredBankAccount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.example.banking.adapter.in.web;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.AssertTrue;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class RegisterBankAccountRequest {
|
||||
|
||||
private String membershipId;
|
||||
private String bankName;
|
||||
private String bankAccountNumber;
|
||||
private boolean isValid;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.example.banking.adapter.out.external.bank;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class BankAccount {
|
||||
|
||||
private String bankName;
|
||||
private String bankAccountNumber;
|
||||
private boolean isValid;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.example.banking.adapter.out.external.bank;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.example.banking.application.port.out.RequestBankAccountInfoPort;
|
||||
import org.example.common.ExternalSystemAdapter;
|
||||
|
||||
@ExternalSystemAdapter
|
||||
@RequiredArgsConstructor
|
||||
public class BankAccountAdapter implements RequestBankAccountInfoPort {
|
||||
|
||||
@Override
|
||||
public BankAccount getBankAccountInfo(GetBankAccountRequest request) {
|
||||
return new BankAccount(request.getBankName(), request.getBankAccountNumber(), true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.example.banking.adapter.out.external.bank;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class GetBankAccountRequest {
|
||||
|
||||
private String bankName;
|
||||
private String bankAccountNumber;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.example.banking.adapter.out.persistence;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "registered_bank_account")
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class RegisteredBankAccountJpaEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long registeredBankAccountId;
|
||||
private String membershipId;
|
||||
private String bankName;
|
||||
private String bankAccountNumber;
|
||||
private boolean linkedStatusIsValid;
|
||||
|
||||
public RegisteredBankAccountJpaEntity(String membershipId, String bankName, String bankAccountNumber, boolean linkedStatusIsValid) {
|
||||
this.membershipId = membershipId;
|
||||
this.bankName = bankName;
|
||||
this.bankAccountNumber = bankAccountNumber;
|
||||
this.linkedStatusIsValid = linkedStatusIsValid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.example.banking.adapter.out.persistence;
|
||||
|
||||
import org.example.banking.domain.RegisteredBankAccount;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class RegisteredBankAccountMapper {
|
||||
|
||||
public RegisteredBankAccount mapToDomainEntity(RegisteredBankAccountJpaEntity membershipJpaEntity) {
|
||||
return RegisteredBankAccount.generateRegisteredBankAccount(
|
||||
new RegisteredBankAccount.RegisteredBankAccountId(membershipJpaEntity.getRegisteredBankAccountId()+""),
|
||||
new RegisteredBankAccount.MembershipId(membershipJpaEntity.getMembershipId()),
|
||||
new RegisteredBankAccount.BankName(membershipJpaEntity.getBankName()),
|
||||
new RegisteredBankAccount.BankAccountNumber(membershipJpaEntity.getBankAccountNumber()),
|
||||
new RegisteredBankAccount.LinkedStatusIsValid(membershipJpaEntity.isLinkedStatusIsValid())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.example.banking.adapter.out.persistence;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.example.banking.application.port.out.RegisterBankAccountPort;
|
||||
import org.example.banking.domain.RegisteredBankAccount;
|
||||
import org.example.common.PersistenceAdapter;
|
||||
|
||||
@PersistenceAdapter
|
||||
@RequiredArgsConstructor
|
||||
public class RegisteredBankAccountPersistenceAdapter implements RegisterBankAccountPort {
|
||||
|
||||
private final SpringDataRegisteredBankAccountRepository bankAccountRepository;
|
||||
|
||||
@Override
|
||||
public RegisteredBankAccountJpaEntity createRegisteredBankAccount(RegisteredBankAccount.MembershipId membershipId, RegisteredBankAccount.BankName bankName, RegisteredBankAccount.BankAccountNumber bankAccountNumber, RegisteredBankAccount.LinkedStatusIsValid linkedStatusIsValid) {
|
||||
return bankAccountRepository.save(
|
||||
new RegisteredBankAccountJpaEntity(
|
||||
membershipId.getMembershipId(),
|
||||
bankName.getBankName(),
|
||||
bankAccountNumber.getBankAccountNumber(),
|
||||
linkedStatusIsValid.isLinkedStatusIsValid()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.example.banking.adapter.out.persistence;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface SpringDataRegisteredBankAccountRepository extends JpaRepository<RegisteredBankAccountJpaEntity, Long> {
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.example.banking.application.port.in;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.example.common.SelfValidating;
|
||||
|
||||
import javax.validation.constraints.AssertTrue;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
@Builder
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class RegisterBankAccountCommand extends SelfValidating<RegisterBankAccountCommand> {
|
||||
|
||||
@NotEmpty
|
||||
private final String membershipId;
|
||||
|
||||
@NotEmpty
|
||||
private final String bankName;
|
||||
|
||||
@NotEmpty
|
||||
private final String bankAccountNumber;
|
||||
|
||||
@AssertTrue
|
||||
private final boolean isValid;
|
||||
|
||||
public RegisterBankAccountCommand(String membershipId, String bankName, String bankAccountNumber, boolean isValid) {
|
||||
this.membershipId = membershipId;
|
||||
this.bankName = bankName;
|
||||
this.bankAccountNumber = bankAccountNumber;
|
||||
this.isValid = isValid;
|
||||
|
||||
this.validateSelf();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.example.banking.application.port.in;
|
||||
|
||||
import org.example.banking.domain.RegisteredBankAccount;
|
||||
|
||||
public interface RegisterBankAccountUseCase {
|
||||
|
||||
RegisteredBankAccount registerBankAccount(RegisterBankAccountCommand command);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.example.banking.application.port.out;
|
||||
|
||||
import org.example.banking.adapter.out.persistence.RegisteredBankAccountJpaEntity;
|
||||
import org.example.banking.domain.RegisteredBankAccount;
|
||||
|
||||
public interface RegisterBankAccountPort {
|
||||
|
||||
RegisteredBankAccountJpaEntity createRegisteredBankAccount(
|
||||
RegisteredBankAccount.MembershipId membershipId,
|
||||
RegisteredBankAccount.BankName bankName,
|
||||
RegisteredBankAccount.BankAccountNumber bankAccountNumber,
|
||||
RegisteredBankAccount.LinkedStatusIsValid linkedStatusIsValid
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.example.banking.application.port.out;
|
||||
|
||||
import org.example.banking.adapter.out.external.bank.BankAccount;
|
||||
import org.example.banking.adapter.out.external.bank.GetBankAccountRequest;
|
||||
|
||||
public interface RequestBankAccountInfoPort {
|
||||
|
||||
BankAccount getBankAccountInfo(GetBankAccountRequest request);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.example.banking.application.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.example.banking.adapter.out.external.bank.BankAccount;
|
||||
import org.example.banking.adapter.out.external.bank.GetBankAccountRequest;
|
||||
import org.example.banking.adapter.out.persistence.RegisteredBankAccountJpaEntity;
|
||||
import org.example.banking.adapter.out.persistence.RegisteredBankAccountMapper;
|
||||
import org.example.banking.application.port.in.RegisterBankAccountCommand;
|
||||
import org.example.banking.application.port.in.RegisterBankAccountUseCase;
|
||||
import org.example.banking.application.port.out.RegisterBankAccountPort;
|
||||
import org.example.banking.application.port.out.RequestBankAccountInfoPort;
|
||||
import org.example.banking.domain.RegisteredBankAccount;
|
||||
import org.example.common.UseCase;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@UseCase
|
||||
@Transactional
|
||||
@RequiredArgsConstructor
|
||||
public class RegisterBankAccountService implements RegisterBankAccountUseCase {
|
||||
|
||||
private final RegisterBankAccountPort registerBankAccountPort;
|
||||
private final RegisteredBankAccountMapper mapper;
|
||||
private final RequestBankAccountInfoPort requestBankAccountInfoPort;
|
||||
|
||||
@Override
|
||||
public RegisteredBankAccount registerBankAccount(RegisterBankAccountCommand command) {
|
||||
|
||||
BankAccount accountInfo = requestBankAccountInfoPort.getBankAccountInfo(new GetBankAccountRequest(command.getBankName(), command.getBankAccountNumber()));
|
||||
|
||||
boolean accountIsValid = accountInfo.isValid();
|
||||
|
||||
if (accountIsValid) {
|
||||
|
||||
RegisteredBankAccountJpaEntity savedAccountInfo = registerBankAccountPort.createRegisteredBankAccount(
|
||||
new RegisteredBankAccount.MembershipId(command.getMembershipId() + ""),
|
||||
new RegisteredBankAccount.BankName(accountInfo.getBankName()),
|
||||
new RegisteredBankAccount.BankAccountNumber(accountInfo.getBankAccountNumber()),
|
||||
new RegisteredBankAccount.LinkedStatusIsValid(true)
|
||||
);
|
||||
|
||||
return mapper.mapToDomainEntity(savedAccountInfo);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package org.example.banking.domain;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Value;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class RegisteredBankAccount {
|
||||
|
||||
private final String registeredBankAccountId;
|
||||
private final String membershipId;
|
||||
private final String bankName;
|
||||
private final String bankAccountNumber;
|
||||
private final boolean linkedStatusIsValid;
|
||||
|
||||
|
||||
public static RegisteredBankAccount generateRegisteredBankAccount(
|
||||
RegisteredBankAccountId registeredBankAccountId,
|
||||
MembershipId membershipId,
|
||||
BankName bankName,
|
||||
BankAccountNumber bankAccountNumber,
|
||||
LinkedStatusIsValid linkedStatusIsValid
|
||||
) {
|
||||
return new RegisteredBankAccount(
|
||||
registeredBankAccountId.registeredBankAccountId,
|
||||
membershipId.membershipId,
|
||||
bankName.bankName,
|
||||
bankAccountNumber.bankAccountNumber,
|
||||
linkedStatusIsValid.linkedStatusIsValid
|
||||
);
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class RegisteredBankAccountId {
|
||||
|
||||
String registeredBankAccountId;
|
||||
|
||||
public RegisteredBankAccountId(String value) {
|
||||
this.registeredBankAccountId = value;
|
||||
}
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class MembershipId {
|
||||
|
||||
String membershipId;
|
||||
|
||||
public MembershipId(String value) {
|
||||
this.membershipId = value;
|
||||
}
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class BankName {
|
||||
|
||||
String bankName;
|
||||
|
||||
public BankName(String value) {
|
||||
this.bankName = value;
|
||||
}
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class BankAccountNumber {
|
||||
|
||||
String bankAccountNumber;
|
||||
|
||||
public BankAccountNumber(String value) {
|
||||
this.bankAccountNumber = value;
|
||||
}
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class LinkedStatusIsValid {
|
||||
|
||||
boolean linkedStatusIsValid;
|
||||
|
||||
public LinkedStatusIsValid(boolean value) {
|
||||
this.linkedStatusIsValid = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.example.banking.adapter.in.web;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.example.banking.domain.RegisteredBankAccount;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.filter.CharacterEncodingFilter;
|
||||
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
class RegisterBankAccountControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext ctx;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper mapper;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx)
|
||||
.addFilters(new CharacterEncodingFilter("UTF-8", true))
|
||||
.alwaysDo(print())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerBankAccountTest() throws Exception {
|
||||
RegisterBankAccountRequest request =
|
||||
new RegisterBankAccountRequest("1", "국민은행", "1234567890", true);
|
||||
|
||||
RegisteredBankAccount bankAccount = RegisteredBankAccount.generateRegisteredBankAccount(
|
||||
new RegisteredBankAccount.RegisteredBankAccountId("1"),
|
||||
new RegisteredBankAccount.MembershipId("1"),
|
||||
new RegisteredBankAccount.BankName("국민은행"),
|
||||
new RegisteredBankAccount.BankAccountNumber("1234567890"),
|
||||
new RegisteredBankAccount.LinkedStatusIsValid(true)
|
||||
);
|
||||
|
||||
mockMvc.perform(
|
||||
MockMvcRequestBuilders.post("/banking/account/register")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(mapper.writeValueAsString(request))
|
||||
)
|
||||
.andExpect(MockMvcResultMatchers.status().isOk())
|
||||
.andExpect(MockMvcResultMatchers.content().string(mapper.writeValueAsString(bankAccount)))
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.example.common;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Component
|
||||
public @interface ExternalSystemAdapter {
|
||||
|
||||
@AliasFor(annotation = Component.class)
|
||||
String value() default "";
|
||||
}
|
||||
@@ -3,7 +3,7 @@ plugins {
|
||||
}
|
||||
|
||||
group 'org.example.membership'
|
||||
version '1.0.1'
|
||||
version '1.0.0'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
rootProject.name = 'bobby-pay'
|
||||
include 'membership-service'
|
||||
include 'common'
|
||||
include 'banking-service'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user