개인적 공부의 중심이 있기 때문에 기존에 개발하던 방식을 모두 버려가며 진행하고 있다. 중요 포인트만 개인적으로 정리하는 글이 될 예정이다.
생산성을 목표로 한다면, 우선은 백엔드 로직과 함께 모든 MSA의 API를 구현 하고, 프론트엔드 화면을 구현하는게 가장 이상적이라고 생각한다. 하지만 나의 경우에는 중간 개발과정을 눈으로 확인하고 싶은 마음이 강했다. 우선 1개의 마이크로서비스의 백엔드 개발이 끝나면, 바로 프론트엔드도 이에 맞춰 개발 하려고 한다. 이 과정에서도 우선은 기능적인 부분 먼저 끝내놓고, 나중에 부족한 부분을 채우려고 한다. 그래서 우선은 각 마이크로 서비스를 대상으로 구현의 단계와 사이클은 다음과 같다.
도메인 정의 -> DB 설계 및 JPA 매핑 -> 비지니스 로직 구현 -> (소·중규모) 테스트 -> API 구현 -> API 테스트 -> 프론트 엔드 화면 구현 -> 대규모(E2E) 테스트 -> 에러 & 보안 로직 구현 및 처리 -> 이전 테스트 반복 및 기능 보완 등 -> 개별 MSA 구현 완료
주요 포인트만 설명하면 다음과 같다.
테스트에 대한 부분은 책 구글 엔지니어는 이렇게 일한다 에서 정의한 테스트 개념을 사용하기로 한다. 기존의 테스트의 개념에서 사용되는 단위테스트, 통합테스트를 조금 더 명확하게 해당 책에서는 정의하고 있고, 나는 책에서 설명하는 작은 ~ 큰 규모의 테스트를 소·중·대 의 명칭으로 사용하기로 한다. 아주 간단하게만 정의하면 다음과 같다.
소규모 테스트: 단 하나의 프로세스에서만 실행되며, IO 등의 연산이나 통신 등이 포함되어 있지 않는 테스트로 아주 빠르게 실행되고 테스트 할 수 있는 테스트
중규모 테스트: 여러 프로세스나 스레드에서 실행 될 수 있으며, 로컬 호스트와 네트워크 통신 등이 허용되는 테스트
대규모 테스트: 무엇이든지 가능한 테스트로 네트워크 통신 등과 같은 이유로 테스트가 비결정적이 될 수 있으며 위험성도 큰 테스트
나의 경우 구체적인 사용으로는 단일 코드 그 자체만으로 테스트 가능하면 소규모 테스트.
IO, DB와 같이 통신을 하거나, 다른 코드와의 연결된 동작을 테스트 해야 한다면 중규모 테스트.
외부의 비결정적인 요소나 통신(네트워크)이 포함되거나 단순히 테스트 코드의 작성만으로 테스트를 할 수 없거나, 범위를 벗어난 경우를 대규모 테스트로 보려고 한다. (명확한 개념은 아니지만 편의상 이렇게 쓰려고 한다)
이전 단계의 요구사항 정의에서 확정된 것과 같이 SPA(Single-Page Application)의 구현을 하고, 이는 SvletKit을 이용하기로 한다. 프론트엔드는 중요한 학습요소로 생각하고 있지는 않다. 디자인이나 전체적인 구현도 AI를 통한 구현으로 넘길 예정이다.
원칙적으로 생각 했을 때에는 비지니스 로직을 구현하면서 자연스럽게 에러와 보안 처리에 대한 부분을 같이 하는게 맞다고 본다. 그리고 내가 실무에서 일했을 때에도 상대적으로 이쪽부분이 많이 취약했던 것 같기도 했다 (개발 일정에 시간이 쫓기기 때문에...) 하지만 실제로 문제가 터지면 감당하기 힘든 부분이 에러나 보안쪽이라고 생각하며, 중요도 또한 매우 높다.
이런 상황에서도 나는 과감히 우선 전체적인 화면 구현 이후에 다시 추가 하기로 한다. 일부 공통적으로 처리 할 수 있는 부분이기에 마지막에 생각하는 게 좀 더 좋을 것 같기도 하고, 바로 운영 단계로 넘어가지도 않을 것이기 때문이다. 그래서 비지니스 로직과 화면 구현을 우선의 목표를 삼기로 한다.
public class UserEntity {
private Long id;
private String nickName;
private Email email;
OauthInfo oauthInfo;
Profile profile;
private int points = 0; // 명시적으로 초기값 입력함
}
가장 기본적인 USER 도메인을 정의했다. 처음에 고민했던 점은
- 순수한 자바 코드로서의 도메인 코드를 유지하고, JPA 매핑을 위한 도메인 엔티티를 별도로 만드는 방법. 2개의 코드로 분리 관리.
- 도메인코드와, JPA 엔티티 (매핑을 위한 코드) 코드를 같이 관리하는 방법. 1개의 코드로 통합 관리.
을 고민 했었다. DDD를 추구하고는 있지만, 1번의 방법을 사용하게 되면 도메인 코드와 JPA 엔티티 코드간의 상호 변환을 계속해야하는 불편함과 코드의 양이 많아진다는 단점이 있다.
2번의 방법을 사용하면 1번 방식에서 처럼 불필요한 코드가 없지만, JPA 기술 만을 위한 코드와 메서드 등으로 도메인 코드 자체를 해석하기가 조금 힘들 수 있으며, JPA에 강 의존한다는 단점이 있다.
나의 선택은 2번의 한꺼번에 관리하는 방식을 택하기로 했다. 시스템의 복잡도가 그리 크지 않다는 생각에 DDD를 추구하긴 하지만, 약간의 트레이드 오프를 선택하기로 했다.
UserEntity에서 Email, OauthInfo, Profile 과 같이 별도로 관리 할 수 있거나, 책임을 가질 수 있을만한 값들은 성격에 맞게 모아서 별개의 값 객체로 정의 했다.
구현한 OauthInfo는 아래와 같다. 회원 가입과 로그인에서 인증부분은 Oauth2.0을 사용하기로 정했고, 그와 관련된 값을 따로 관리 하기위해 정의 했다.
public class OauthInfo {
private Provider provider;
private String providerId;
}
public enum Provider {
GOOGLE,
FACEBOOK,
KAKAO
}
OauthInfo는 또 다른 값 객체를 가지고 있는데, 이는 enum 타입으로 정의된 객체다. Oauth를 인증하는 공급자(Provider) 정보를 한번에 관리하고, 추가가 필요한 경우 쉽게 추가도 가능하도록 했다.
이런 식으로 값 객체를 통해서 단순히 원시 타입을 통해 도메인을 정의하는 방식이 아닌, 역할과 책임을 생각해 보며 도메인을 정의 해 보는 연습을 하고 있다.
@Entity
@Table(name = "USERS")
@SecondaryTables({
@SecondaryTable(name = "OAUTH_INFO", pkJoinColumns = @PrimaryKeyJoinColumn(name = "user_id")),
@SecondaryTable(name = "PROFILE", pkJoinColumns = @PrimaryKeyJoinColumn(name = "user_id"))
})
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class UserEntity extends BaseJPAEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nickName;
private Email email;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "provider", column = @Column(table = "OAUTH_INFO", name = "provider")),
@AttributeOverride(name = "providerId", column = @Column(table = "OAUTH_INFO", name = "provider_id"))
})
OauthInfo oauthInfo;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "profileImage", column = @Column(table = "PROFILE", name = "profile_image")),
@AttributeOverride(name = "bio", column = @Column(table = "PROFILE", name = "bio"))
})
Profile profile = Profile.defaultProfile();
private int points = 0;
public void updateNickName(String nickName) {
this.nickName = nickName;
}
public static UserEntity create(String nickName, Email emailAddress,
OauthInfo oauthInfo) {
return new UserEntity(nickName, emailAddress, oauthInfo);
}
public void updateProfile(Profile profile) {
this.profile.updateProfile(profile);
}
public static UserEntity fromOAuth2UserInfo(OAuth2UserInfo userInfo) {
Email email = Email.createFromFullAddress(userInfo.getEmail());
OauthInfo oauthInfo = OauthInfo.create(userInfo.getProvider(), userInfo.getProviderId());
return UserEntity.create(userInfo.getName(), email, oauthInfo);
}
private UserEntity(String nickName, Email email,
OauthInfo oauthInfo) {
this.nickName = nickName;
this.email = email;
this.oauthInfo = oauthInfo;
}
}
일부 코드의 변경, 추가 가능성이 있지만 현재 상태에서의 USER 도메인 코드이다.
USER 엔티티와 함께 있는 OauthInfo, Profile은 USER 객체의 생성과 함께 관리되는 객체이며, USER의 생성과 소멸과 같은 생애주기를 같는 객체가 된다. USER는 부모 객체이며 ROOT가 되는 객체로, DDD에서 여러 객체를 한꺼번에 묶는 단위로 애그리거트라는 개념을 사용한다. USER는 ROOT 애그리거트가 되며, 나는 특별히 OauthInfo, Profile은 DB에서 별도의 테이블로 관리 할 대상으로 삼았다.
보통 JPA에서 다른 테이블로 관리를 할 때 가장 쉽게 구현 할 수 있는 방법은 @Entity를 사용해서 별도의 테이블로 만들 클래스를 정의하고, @Onetoone @ManyToOne 과 같은 연관관계 매핑을 통해서 관계를 맺어주는 방식이 가장 쉽기도 하면서 논리적으로 이해하기가 비교적 쉬운 편이다.
하지만 나의 경우에는 User라는 ROOT 엔티티의 중요성을 코드로 나타내기 위해서 @SecondaryTables 어노테이션을 통해서 하위 객체의 관계를 매핑했다.
따로 관리하게 될 테이블의 정의와 함께, @AttributeOverride를 통해 관계를 또 일일이 정의 해 주는 코드를 추가한다.
JPA와 관련된 코드들이 추가되면서 코드 자체의 가독성이 떨어졌다. 결국 도메인 코드와 JPA 엔티티 코드의 분리를 선택하는게 좋은 선택지였다는 것을 이 단계에서 확인했다. (하지만 따로 분리는 하지 않을 예정... 앞으로 생성될 다른 마이크로 서비스에서 분리 관리 하기로 한다.)
UserEntity에서 상속을 통해 다음과 같은 코드를 공통 컬럼을 사용하기로 한다. 실무에서 보통 데이터의 추적을 목적으로 생성, 수정 시간과 함께 그 행동을 한 주체 (주로 사용자 ID를 사용한다)의 정보를 남기곤 한다. 나는 우선은 데이터의 생성과 변경 시간만 추가하기로 했다.
아래와 같이 코드를 만들었는데, 직접적으로 인스턴스를 생성할 일이 없으므로 추상클래스로 선언 했다. 사용한 어노테이션 중 @CreationTimestamp, @UpdateTimestamp는 거의 동일한 동작을 하는 @CreatedDate, @LastModifiedDate로 대체 할 수 도 있다. 하지만 세부적인 사항이나 사용법이 약간 다른데, 자세한 내용은 Entity 생성일시 수정일시 자동 입력 방법 2가지 이곳에서 확인 해 보면 좋을 것 같다.
@MappedSuperclass
public abstract class BaseJPAEntity {
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
}
결론적으로는 백엔드에서 소셜 로그인 구현하기 이 글을 보고 쉽게 구현을 완료 했다. 스프링 시큐리티를 통해 비교적 쉽게 Oauth2.0을 구현 할 수 있다. 또 방식은 JWT를 사용하며, JWT의 단점을 보완하기 위해 RefreshToken 또한 함께 구현 했고, 이 과정과 개념에 대한 자세한 내용이 이 블로그 글에 포함되어있다. 100% 이해하고 개발하는 것도 좋겠지만, 이쪽은 상당부분 따라하면서 추상화 된 부분을 그냥 그렇구나 하고 넘어간 부분이 많다. 그리고 시큐리티쪽 설정은 일단은 다 풀어놓은 상태인데, 이쪽부분은 위에서 언급했던 것과 같이, 보안 부분을 따로 처리 하는 시점에서 전체적으로 수정 해 볼 생각이다.
이 로그인과 관련된 부분은 원 블로그 글 자체만으로 충분하다고 생각하며 따로 정리는 하지 않겠다.
RefreshToken은 Redis를 통해서 구현하기로 한다. 대규모 트래픽 상황이라고 가정하면 실제 DB의 부하 분산 역할과 함께, Redis에서 특정 데이터의 값이 존재하는 시간을 로그인 유지 시간 개념으로 처리하면 로그인과 관련된 로직을 redis로 처리하는 것이 더 효율적일 수 있다.
나는 최대한 테스트가 용이하도록 적어도 로컬 테스트 단계에서는 별도로 Redis를 설치 하지 않고도 사용 할 수 있도록 embeded redis를 사용하기로 했다. 즉 H2 DB와 비슷하게 로컬에서 별다른 설정이 필요 없이 프로젝트가 실행되면 자동으로 동작하는 데이터 서버 구성이 필요했다.
우선 필요한 의존성은 아래와 같다.
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation group: 'com.github.codemonstur', name: 'embedded-redis', version: '1.4.3'
spring-boot-starter-data-redis
는 SpringData JPA의 Redis 버전이라고 생각하면 된다. Redis를 사용할 때, CRUD 로직을 별도로 구현할 필요가 없거나, 쿼리 메서드 방식 등을 이용해 쉽게 필요한 로직을 구현 할 수 있다.
embedded-redis
는 공식 라이브러리는 아니지만 나는 위 버전을 사용하기로 한다. 현재는 이 프로젝트가 스프링 최신 버전에 대응하도록 계속 업데이트가 진행중인 버전인 것을 확인 했다.
그리고 설정과 실행을 위한 코드의 추가와, 이 설정 값의 추가를 해 준다.
@Configuration
@Getter
public class RedisProperties {
private int redisPort;
private String redisHost;
public RedisProperties(
@Value("${spring.data.redis.port}") int redisPort,
@Value("${spring.data.redis.host}") String redisHost)
{
this.redisPort = redisPort;
this.redisHost = redisHost;
}
}
@Configuration
public class RedisEmbeddedConfig {
private final RedisServer redisServer;
public RedisEmbeddedConfig(RedisProperties redisProperties) throws IOException {
this.redisServer = new RedisServer(redisProperties.getRedisPort());
}
@PostConstruct
public void postConstruct() throws IOException {
redisServer.start();
}
@PreDestroy
public void preDestroy() throws IOException {
redisServer.stop();
}
}
@Configuration
@EnableRedisRepositories
public class RedisConfiguration {
@Bean
public LettuceConnectionFactory redisConnectionFactory(
RedisProperties redisProperties) {
return new LettuceConnectionFactory(
redisProperties.getRedisHost(),
redisProperties.getRedisPort());
}
@Bean
public RedisTemplate<?, ?> redisTemplate(LettuceConnectionFactory connectionFactory) {
RedisTemplate<byte[], byte[]> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
return template;
}
}
application.yml 설정파일
spring:
data:
redis:
host: localhost
port: 6370
복잡해 보이긴 하지만, 설정과 관련된 부분은 이해하기 보다는 그냥 이렇게 쓰면 되는구나 정도로 넘어가도록 한다. RedisEmbeddedConfig 에서 프로젝트가 실행 되었을 때, 직접적인 서버의 실행과 정지를 담당한다. 여기에 따로 로깅을 남기는 방식을 통해서 Redis 서버의 정상적인 실행을 확인 하는 코드를 넣을 수도 있을 것이다.
모든 작업이 끝났으면 기존의 JPA를 사용했던 Token 엔티티 코드를 Redis에 맞도록 변경하고, Repository 등도 함께 변경하면 끝이다. 이 과정은 따로 정리 하지는 않는다. 어노테이션과 구현체 정도만 바꾸는 것으로 data-jpa 에서 data-redis의 전환이 쉽게 가능하다. 나는 이 과정에서 따로 프로젝트의 인자 값을 이용해서 구현체를 전환 하면서 JPA를 사용 할 수도, Redis를 사용 할 수도 있게 바꾸려고 시도 했으나 실패했다.
(JPA, Redis의 선택은 처리가 가능했는데, Security와 Oauth 구현의 필터쪽에서 순환 참조 문제가 발생했다. 구조를 점진적으로 변경하면서 도전했으나 고쳐야 할 부분이 너무 커져서 하지 않기로 했다.)
테스트가 쉽고, 별다른 구성을 하지 않아도 Redis를 사용 할 수 있도록 embedded redis를 구현 완료 했다. 그런데 막상 Redis 자체의 테스트는 조금 애 먹었는데, 맥 환경에서는 redis-cli를 별도로 설치해서 쉽게 테스트 가능 하지만, 윈도우에서는 약간 힘든 점을 발견했다. 그래서 결국은 대규모 테스트 (실제 토큰 발급과 함께, 토큰 값을 정상적으로 찾아오는지 확인하는 테스트용 엔드 포인트를 만들었다) 를 통해서 테스트를 확인 할 수 밖에 없었다.
결론적으로는 별도의 설치 없이 Redis를 사용 할 수 있는 프로젝트의 수준으로 만족해야 했다.
프로젝트에서 DB 접속 정보나, API 인증 키, 기타 접속 정보 등 여러가지로 외부에 노출되면 곤란한 정보 or 내부 개발자도 보안을 위해 일부만 알도록 제한 해야 하는 정보가 있다. 지금 USER서비스의 경우 OAUTH의 인증에 필요한 클라이언트 ID, SECRET 값 같은 경우는 비밀로 다뤄야 할 정보이다. 이 정보의 경우 프로젝트 내에서는 application.yml 과 같은 설정에 값으로 관리하면 편하게 관리는 가능하지만 이렇게 되면 Git과 같은 형상관리에서 문제가 생긴다.
외부로 공개 할 수는 없기에 해당 정보를 입력한 상태 자체는 따로 commit과 push를 하지 않는 방식을 유지하거나, 별도의 파일을 만들고 해당 파일은 gitIgnore에 넣어서 관리하는 방식도 생각 해 볼 수 있는데 아무래도 좋은 방법은 아니기에 다른 방법을 찾고 있었다.
이 민감한 값에 대한 관리에 대한 정답은 Spring Vault
를 이용하는 것이다.
스프링 클라우드 환경에서 공통으로 민감한 값에 대해 관리하고, 안전하게 처리 할 수 있는 장점이 있다. 하지만 이 설정을 하는 순간 Vault 서버에 대한 접근과 인증 인가가 끝난 후에, 따로 관리 하기 원하는 민감한 값에 접근 할 수 있게되는 의존성이 생기고, 이 요구사항 자체가 테스트를 어렵게 하는 단점이 생긴다. 그래서 개발 단계에서 일단은 민감한 값을 관리하기 위한 목적으로 별도의 값을 관리 할 수 있도록 하는 방법을 찾아서 선택하기로 한다.
implementation 'io.github.cdimascio:java-dotenv:5.2.2'
위 의존성을 추가 하고, resources 하위에 .env 파일을 생성한다. 그리고 그 파일안에 민감한 값을 적어 주면 된다.
OAUTH_GOOGLE_CLIENT_ID=여기에 필요한 값 넣기
OAUTH_GOOGLE_CLIENT_SECRET=여기에 필요한 값 넣기
JWT_SECRET=여기에 필요한 값 넣기
이런 식으로 따로 관리할 값을 .env 파일에 넣어주면 된다. 리눅스계열에서 . 이 붙은 파일은 숨김파일이며, 보통 특정한 환경값을 .env 파일에 기술하고 하는데 이 익숙한 형태를 스프링에서도 사용하기 위해 개발 된 것 같다.
@SpringBootApplication
public class UserApplication {
public static void main(String[] args) {
Dotenv dotenv = Dotenv.load();
// Set environment variables
dotenv.entries().forEach(entry -> {
System.setProperty(entry.getKey(), entry.getValue());
});
SpringApplication.run(UserApplication.class, args);
}
}
그리고 스프링 어플리케이션에 해당 값을 불러오는 코드를 넣어주면 된다. Key와 Value로 연결되는 값을 불러다가 스프링 어플리케이션을 시작할 때 해당 값을 넣어주는 간단한 원리의 구조를 갖는다.
그리고 이제 .env 파일은 gitIgnore에 등록한 후, 이제부터 안심하고 프로젝트의 모든 변경 사항을 안전하게 Commit 할 수 있다.
운영단계 이전까지는 이 구조로 민감한 값을 관리할 예정이다.
테스트 코드는 전적으로 Junit을 통해서 작성 했다. 최대한 BDD(Behaviour-Driven Development) 형식으로 어떤 행동 내지는 행위가 일어났을 때를 테스트 코드로 작성 해보려고 했다. 이 테스트코드에 대한 부분에서 고민이 많았다.
소규모 테스트 레벨의 도메인 코드와 그 비지니스 로직에 대한 테스트는 별 문제가 없었다. 하지만 중규모 테스트로만 넘어가도 많은 문제를 겪게 되었다. 우선 아주 간단하게는 JPA를 사용하는 경우에 해당 된다. H2 DB를 이용해서 자체 메모리 DB를 사용한다고 해도 레이어 계층에서의 문제가 있다. 간단하게 Repository 레이어의 테스트는 @DataJpaTest 을 사용하면 된다. 하지만 개인적인 생각으로는 그리 큰 의미가 있는 테스트인가 라는 의문이 있다. SpringDataJPA를 통해서 이 부분은 추상화를 통해 사용하게 되는데, 직접적인 네이티브 쿼리로 구현하지 않는 이상 해당 Repository의 기능은 이미 Hibernate에서 충분히 테스트를 거쳐서 나온 코드이기 때문이다. SpringDataJPA에 대한 사용에 대한 믿음(?) 이 있다면 따로 테스트를 하지 않아도 괜찮다고 생각한다.
그래서 실제로 문제되는 레이어 계층은 Service 레이어가 된다.
@SpringBootTest을 통해서 모든 객체를 주입받고 테스트를 시작해야 하는데, 이 과정에서 테스트 코드 자체의 준비 시간이 길어지는 것을 첫 시작으로 문제점을 인식하게 된다.
또 서비스 레이어의 테스트 코드가 정상적으로 동작하는 것을 확인 했으나, 시간이 지나고 나서 실패하는 일을 겪었는데 스프링 시큐리티와 같은 추가로 개발한 해당 테스트 코드와는 전혀 상관 없는 설정 값 때문에 프로젝트의 로딩에 문제가 있어 테스트 코드를 실행하는 것 자체에서 에러를 발생하는 등 크고 작은 문제를 만난다.
그래서 결국은 별도의 test 프로필을 만들고, test 프로필일 경우에는 어떤 설정이 동작하지 않게 하는 방식의 테스트 환경을 위한 별도의 설정을 하는 것으로 해결했다.
생각보다 크고 작은 변경을 통해서 Service 레이어도 문제없는 테스트 코드를 작성했다. 하지만 여기서 아직까지도 의문으로 남긴 부분은 과연 확실하게 서비스 계층을 나눠서 테스트하는 과정에서 오는 어려움을 해결하는 것으로 Mocking을 사용하는 것이 맞는가에 대한 의문이다.
다시 앞으로 돌아가서, Service 레이어에서 주입받는 객체를 Mocking을 통해서 가짜 객체를 사용하면 이 의존성 자체가 끊어지게 된다. 이 방식을 사용하면 Presentation 레이어(Controller에 해당)도 소규모 테스트로서 동일하게 처리, 테스트 할 수 있다. 각 레이어를 명확하게 나누고, 특정 레이어 단위에서 테스트가 성공 했다면, 하위나 상위 레이어에서도 테스트가 성공 할 것으로 본다면 이 전체 테스트는 성공 한다고 볼 수 있다.
하지만 개인적인 생각으로는 Mocking에 대해서는 신뢰하지 않는 편이다. 실무에서 외부 API를 통해 개발 했던 기능에서도 해당 API의 사용법과 개발문서를 보고 예상되는 반환코드와 값 등을 토대로 개발을 했었는데, 막상 실제 API에서 갑작스럽게 예상치 못했던 값이 나와서 문제가 생겼던 경험이 있다. 이런식으로 실제 환경에서 예상되는 값과 전혀 다른 경우가 있기도 하고, 내부 시스템인 경우라고 해도 이것을 보장하지 못하는 경우도 종종 겪었다 (DB 데이터 값의 갑작스런 변경 등)
실제로 테스트 코드를 중요시 하는 환경에서 개발을 경험한 적이 없기 때문에 이 선택에 대해서는 실무적인 해답을 찾아보고 싶다. 이론적으로는 Mocking을 통해 레이어를 명확히 하고, 최대한 소규모 테스트로의 전환이 바람직하다고 보는 입장이나, 개인적인 선호나 신뢰의 문제로 나는 적어도 나의 테스트 코드 작성 과정에서는 Mocking을 조금 지양하는 방식을 선택하기로 했다.
개별적인 결론만을 내린채로 테스트 코드에 대한 부분은 이렇게 마무리 한다. 실제 테스트 코드 작성과 관련된 내용은 따로 작성하진 않는다.
추가로 필요한 프로필 이미지 업로드(파일 업로드) 기능을 포함하여 날림 수준으로 1차 기본 기능은 구현을 마쳤다. USER 마이크로 서비스 자체는 큰 기능이나 로직이 없는 편이기도 했는데, 그럼에도 불구하고 시간을 조금 많이 쓴 느낌도 있다. 아무래도 원래의 큰 고민없이 기능의 초점만을 둔 방식을 버렸기 때문이라고 생각한다. 지금 단계에서도 부족한게 보이는데, 우선은 프론트 엔드 화면을 개발 하고, 이 화면을 적용하면서 보완을 하려고 한다. 글로는 많이 담지 못한 부분도 있고, 명확하게 정리를 하자니 개별 사항의 내용이 방대하기 때문에 결국은 개발 일기장 같은 글이 되었다고 생각한다.
그리 어려운 내용은 없었음에도 고민하고 시간을 쏟는게 많았는데, 아무래도 이 방법 자체가 익숙하지 않기 때문이라고 위안을 삼아보기로 한다. 다음에 구현할 마이크로 서비스는 조금 더 빠르고 견고하게 만들 수 있을 것을 소망하며 이번편을 마친다. 다음에 이어질 글은 우선 USER 서비스만을 위한 프론트엔드 화면의 구현편이다.