Spring Boot + R2DBC(Mariadb) + Next 연동
Spring R2DBC 연동
gradle 설정
먼저 R2DBC + mariadb를 사용하기 위해 spring data r2dbc 와 r2dbc-mariadb(db driver) 를 gradle에 추가한다.
implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
runtimeOnly("org.mariadb:r2dbc-mariadb")
Config 추가
repository를 사용할 것이므로 AppConfig에 @EnableR2dbcRepositories 어노테이션을 추가한다.
R2dbc audit은 JPA audit과 유사하다. @CreatedBy, @CreatedDate, @LastModifiedBy, @LastModifiedDate annotation을 활성화시켜준다.
https://docs.spring.io/spring-data/relational/reference/r2dbc/auditing.html
import org.springframework.context.annotation.Configuration;
import org.springframework.data.r2dbc.config.EnableR2dbcAuditing;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
@Configuration
@EnableR2dbcRepositories
@EnableR2dbcAuditing
class AppConfig {
}
Repository 추가
Account 모델에 대한 Repository를 구성한다. ReactiveCrudRepository<T, ID> 를 상속해서 인터페이스를 만들면 된다.
public interface AccountRepository extends ReactiveCrudRepository<Account, Long> {
}
모델(엔티티) 추가
@Data
@AllArgsConstructor
public class Account {
@Id
private Long id; // @id 어노테이션으로 PK를 지정한다.
@NonNull private String nickname;
private String kakaoId;
private String googleId;
public static Account fromNickname(String nickname) {
return new Account(null, nickname, null, null);
}
}
Account 모델을 만들어서 R2dbc와 연결해준다.
@Data 로 getter, setter 등을 추가하고
@AllArgsConstructor로 생성자를 추가한다.
위의 단순한 보일러플레이트 코드를 lombok으로 편하게 추가할 수 있다.
그리고 팩토리 메소드를 생성하여 nickname을 통해 새로운 계정을 생성할 수 있도록 하였다.(테스트 용)
DB 관련 어노테이션 관련 설명
- @Table annotation을 통해 매핑할 데이터베이스 table을 설정
- @Id annotation을 통해 Primary key를 설정한다.
- @Column annotation을 통해 Table column을 설정한다. 위 예제에서 사용되지 않았듯, 테이블 필드 이름과 동일하다면 사용하지 않아도 된다.
- @CreatedDate, @LastModifiedDate 는 위에서 설명했듯 Audit annotation으로 R2dbc에서 audit을 활성화하면 자동으로 값을 넣어준다.
지원하는 default field type에 대한 내용은 여기를 참조하자.
이 때, @Id annotation 설정 필드는 값을 할당하느냐 안하느냐로 SQL query가 달라진다.
- @Id 필드가 null인 상태로 save 메서드를 실행하면, Insert statement가 실행된다.
- @Id 필드가 값이 설정된 상태로 save 메서드를 실행하면, Update statement가 실행된다.
그리고 r2dbc, jpa 모두 orm 역할을 하지만 DB 마이그레이션까지 해주지는 않는다.
해당 기능을 해주는 도구로는 https://flywaydb.org/ 를 사용하면 된다.
Service, Controller, DTO 구성
아래와 같이 Reactive하게 Service, Controller, Dto를 구성하면 이제 모든 연동이 완료된다.
하나씩 차근차근 살펴보자.
DTO
@Data
public class AccountDto {
Long id;
String nickname;
String kakaoId;
String googleId;
}
단순한 BODY를 받아오기 위한 DTO이다.
@Data 어노테이션을 써서 보일러 플레이트 코드를 제거하였다.
Service
@Service
public class AccountService {
private final AccountRepository accountRepository;
public AccountService(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
public Mono<Account> createAccountByNickname(String nickname) {
return accountRepository.save(Account.fromNickname(nickname));
}
}
생성자 주입을 통해 앞에서 만든 AccountRepository를 빈으로 주입해준다.
그리고 nickname을 통해 계정을 생성하여 accountRepository.save를 호출한다.
ReactiveCrudRepository를 상속받고 있으므로 반환값이 Mono를 지정해야한다.
Controller
@RestController
@RequestMapping("/v1/accounts")
public class AccountController {
private final AccountService accountService;
public AccountController(AccountService accountService) {
this.accountService = accountService;
}
@PostMapping()
public Mono<Account> createAccountByNickname(@RequestBody AccountDto accountDto) {
return accountService.createAccountByNickname(accountDto.getNickname());
}
}
디비 설계
프로젝트의 기본 DB 구조를 구성해봤다. 추후 확장 가능성은 있지만 필수 기능을 먼저 구현하기 위한 디비를 구성하였다.
Layered Architecture 구성
백엔드 프로젝트의 아키텍쳐로는 레이어드 아키텍쳐를 선택했다.
기본적으로 중/대규모 서비스에서 많이 사용하고 (카카오도...) 간략한 장단점은 아래와 같다.
장점
- 유지보수성: 각 구성 요소가 명확한 역할을 가지므로 코드의 이해와 수정이 쉬움
- 재사용성: 특히 서비스와 리포지토리 계층은 다른 컨텍스트에서도 재사용될 가능성이 높음
- 확장성: 애플리케이션의 각 부분이 독립적으로 확장될 수 있어, 새로운 기능 추가나 변경이 용이
- 테스트 용이성: 각 계층이 분리되어 있어, 단위 테스트나 통합 테스트를 수행하기 쉬움
단점
- 초기 개발 속도: 초기 프로젝트 구조 설정에 시간이 소요되며, 작은 프로젝트의 경우 과도한 설계일 수 있음
- 복잡성: 각 계층 간의 상호작용이 복잡해질 수 있어, 프로젝트의 크기가 커질수록 관리하기 어려움
- 오버헤드: 각 계층을 거칠 때마다 추가적인 호출이 발생하여 성능에 영향을 줄 수 있음
아래는 DB 연동 작업 및 실제 호출까지 테스트를 마친 프로젝트의 구조이다.