Home 프로젝트를 잘 관리하려면 어떻게 해야할까?
Post
Cancel

프로젝트를 잘 관리하려면 어떻게 해야할까?

1. 글을 작성하게 된 계기


과거 프로젝트가 커지면서, 기능 추가 가 힘들거나 운영이 버거웠던 적이 있었습니다. 이번에 프로젝트를 진행하며 이를 어떻게 잘 관리할 수 있을지 고민하고 적용해 보았는데, 이 과정에서 알게 된 내용들, 제 생각을 정리하기 위해 글을 작성하게 되었습니다.

이는 정답이 없는 영역으로, 사람마다 생각이 다를 수 있습니다. 의견이 다르다면 편하게 댓글 남겨주세요.





2. 프로젝트 관리


개인적으로 느낀 괜찮은 프로젝트 관리 방법에는 다음과 같은 것들이 있었는데, 이에 대해 하나씩 살펴보도록 하겠습니다.

  1. 개발 환경 분리
  2. 개발 환경 통일
  3. 테스트 코드
  4. 코드 리뷰
  5. 운영 데이터 관리
  6. 인증 토큰 관리
  7. 외부 의존성 제거
  8. 서브 도메인 활용
  9. JetBrain Http
  10. 현재 내 상황 공유





2-1. 개발 환경 분리

개발 환경을 분리해야 합니다. 너무 당연한 이야기지만 그만큼 중요한데요, 개발 환경이 분리되지 않으면 프로젝트가 커질수록 실수할 여지가 생기고, 간단한 기능을 추가하는 데도 애를 먹을 수 있기 때문입니다.

image

현재 프로젝트에서는 설정 파일을 나누고 깃 서브 모듈(Git Submodule)을 통해 티켓으로 관리하고 있는데, 새로 추가된 설정이나 내용을 빠르게 파악할 수 있고, 팀원 간 공유가 잘 돼, 실수할 여지가 적었습니다.







개발/운영 환경의 설정 파일 변수들은 암호화해서 관리하고 있습니다. 로컬 환경은 암호화하지 않고요. 아무리 안전장치가 있더라도 설정값들을 그대로 외부에 노출하면 안 되겠죠?

1
2
3
4
5
6
7
# Local
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/dailyge?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
    username: hello
    password: world
1
2
3
4
5
6
7
# Dev
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: ENC(o7405rmbzU2bRMyOAzr+4opyECJ/LsFRB0FGzO5mZScGoqf2kDkmNhmELNIxoD5uwwaLTU2q4wQBxI529g7KXpxnkJD9pfLGwNhBngcUx7byHO8UY6a7Fg==)
    username: ENC(Z2Upr5JspIww2+nSI3J2Tw==)
    password: ENC(SX6bUA+1hRA8gt6WGP8ukQ==)







2-2. 개발 환경 통일

개발 환경을 통일하면 좋습니다. 개발 환경이 통일돼 있으면 설정하기 위해 사용하는 시간이 줄어들며, 일관된 규칙을 통해 빠르게 팀에 적응할 수 있기 때문입니다.

개발 환경은 다양한 정의, 문맥을 포함할 수 있는데, 개발을 시작하기 위해 하는 설정코드 컨벤션 두 가지로 범위를 좁히겠습니다. 개발을 시작하기 위한 설정은 쉽게 말해 데이터베이스를 연동하고, 설정값을 세팅하는 것을 말합니다.





스프링부트는 3.1버전부터 도커 컴포즈(Docker Compose) 를 지원하기 시작했는데, 이를 사용하면 개발 환경을 통일하기 쉬워집니다. 컨테이너를 사용하기 때문에 심지어 다른 운영체제를 사용하더라도 영향을 받지 않습니다. 이에 대해서는 이전에 작성한 포스팅이 있는데, 이를 참조해 주세요.

Docker Compose support in Spring Boot 3.1 builds on top of the ConnectionDetails abstraction, which we’ve featured in a separate blog post.





코드 컨벤션은 Checkstyle, PMD 같은 협업 툴 을 사용하면 일관된 규칙을 따르고 실수를 방지할 수 있습니다. 규칙을 어길 시 빌드가 되지 않도록 만들고, 이를 자동화 시키면 실수할 여지가 더 줄어들겠죠?

팀에 코드 컨벤션이나 일관된 규칙이 있다면 코드로 빠르게 팀에 기여할 수 있는 것 같습니다. 처음 팀에 들어가서 남이 어떻게 코드를 작성했는지 볼 때, 일관된 규칙이 없다면 어느 것을 따라야 할지부터 혼란스럽잖아요?





개인적으로 개발 자체보다 환경 설정을 하고, 팀 규칙을 살피고 찾는 게 더 힘들고, 시간을 많이 쓰는 편입니다. 그러다 보니 이런 부분에 생각이 많은데요, 다양한 툴들을 활용하면 불필요한 시간을 줄이고 빠르게 개발할 수 있겠죠?

이런 것들이 쌓여 정작 개발해야 할 때 발목을 잡으면 정말 짜증나더라고요. 😡







2-3. 테스트 코드

테스트 코드를 작성하면 새로운 기능을 추가 하거나 유지 보수 할 때 정말 편합니다. 프로젝트가 커지면 조그마한 기능을 수정해도 어떤 사이드 이펙트 가 발생할지 모르거든요. 추가로 정적 분석 툴을 사용해 현재 테스트 코드가 어느 정도까지 커버하는지, 코드 스멜은 없는지 등을 주기적으로 체크하면 더 좋습니다. 이를 통해, 프로젝트가 커지더라도 처음과 같은 개발 속도를 낼 수 있게 되니까요.

image

처음에는 테스트 코드를 작성하지 않는 것이 빠르게 개발하는 것 같지만, 프로그램이 커져서 디버깅하는 시간을 고려하면, 결국 비슷하거나 오히려 속도가 더 느려지게 됩니다.







2-4. 코드 리뷰

코드 리뷰 문화를 도입하면 내가 발견하지 못한 실수/버그 를 찾거나, 다른 사람의 생각을 배울 수 있어 좋습니다. 개발하다보면 내가 보지 못하거나 생각하지 못한 부분이 나오는데, 코드 리뷰를 통해 이런 부분을 보완하는 것이죠. 결국 장기적 으로 잘 운영 하려면 혼자만의 사고보다 다양한 관점 이 필요한 것 같습니다.

image







2-5. 운영 데이터 관리

운영을 위한 데이터 를 별도로 생성/관리하면 좋습니다. 이번 프로젝트에서는 개발 환경에서 애플리케이션이 실행될 때, 운영 데이터 를 초기화하고 있습니다. 주로 회원 관련된 데이터인데요, 다른 기능을 테스트하기 위해서는 반드시 회원 가입이 필요하기 때문입니다. 데이터가 미리 초기화돼 있다면 기능을 빠르게 테스트 할 수 있습니다.

image







처음에는 운영을 위한 코드를 프로젝트에 넣는다고? 라는 생각에 거부감이 들었습니다. 하지만 원활한 운영은 코드를 작성하는 것만큼 중요하더라고요. 버그가 발생해서 빠르게 원인을 파악하고 고쳐야 하는데, 이를 위해 매번 회원 데이터를 저장하고, 토큰을 발급하면 번거롭고 시간이 오래 걸리잖아요?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Component
@RequiredArgsConstructor
public class OperationDataInitializer {

    ......

    @PostConstruct
    public void init() {
        initSchema();
        initCollection();
        initData();
    }

    ......

    // 사용자 데이터 생성
    public void initData() {
        if (isEnv(LOCAL) || isEnv(DEV)) {
            userWriteUseCase.save(new UserJpaEntity(nickname, email));
        }
    }

    ......
}







단, 이렇게 저장한 데이터는 사이드 이펙트를 발생시키지 않도록 애플리케이션 종료 시점 에는 데이터를 삭제하는 것이 좋습니다. 예를 들어, 테스트 서버에서 새로 배포가 됐다면, 기존 데이터가 영향을 주면 안 되겠죠?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
@RequiredArgsConstructor
public class OperationDataInitializer {

    ......

    @PreDestroy
    public void clearData() {
        if (isEnv(LOCAL) || isEnv(DEV)) {
            jdbcTemplate.execute("TRUNCATE users");
            for (final String collectionName : this.collectionNames) {
                mongoTemplate.remove(new Query(), collectionName);
            }
        }
    }

    ......
}







2-6. 인증 토큰 관리

인증/인가에서 만료 기간이 긴 토큰 을 생성하면 기능 테스트를 쉽게 할 수 있습니다. 인증/인가가 필요한 기능을 테스트하기 위해서는 매번 인증 토큰을 생성 해야 하는데, 이때, 외부 API를 호출이 있는 경우, 의존성 이 생겨 꽤 번거롭습니다. 이를 위해 프로젝트에서는 1,000년짜리 액세스 토큰(Access Token)을 만들어 관리하고 있는데, 이를 통해 인증/인가 부분에 대한 번거로움을 덜 수 있었습니다. 특히 테스트 관련된 부분에서요.

1
2
3
4
5
6
7
{
  "exp": 32503625558,
  "sub": "XXXXXXXXXy@gmail.com",
  "id": 359,
  "nickname": "XXX",
  "role": "NORMAL"
}

단, 토큰이 외부에 유출될 경우, 악용될 사례가 있으므로 안전하게 관리하도록 합니다.





이는 문서화를 위한 테스트인수 테스트 가 작성돼 있다면, 정말 편리하게 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@DisplayName("[DocumentationTest] 태스크 등록 문서화 테스트")
class TaskRegisterDocumentationTest extends DatabaseTestBase {

    ......

    @Test
    @DisplayName("할 일을 등록하면 201 Created 응답을 받는다.")
    void whenRegisterTaskThenStatusCodeShouldBe_201() throws Exception {
        
        ......

        given(this.specification)
            .filter(document(
                IDENTIFIER,
                TASK_AUTHORIZATION_HEADER,
                TASK_CREATE_REQUEST_SNIPPET,
                TASK_CREATE_RESPONSE_SNIPPET
            ))
            .contentType(APPLICATION_JSON_VALUE)
            .header(AUTHORIZATION, getAuthorizationHeader())  // 미리 생성한 토큰
            .header(USER_ID_KEY, newUser.getId())
            .body(objectMapper.writeValueAsString(request))
            .when()
            .post("/api/tasks")
            .then()
            .statusCode(201);
    }
}







2-7. 외부 의존성 제거

외부 의존성이 있는 경우, 이를 제거할 방법이 필요합니다. 외부 의존성이란 애플리케이션이 데이터베이스, 또는 외부 API 호출 과 같이 애플리케이션 외부에 의존성을 가지는 것 을 말합니다. 외부 의존성이 있다면, 외부 상황 에 따라 결과가 달라질 수 있기 때문에, 우리 애플리케이션은 항상 불안정한 상태가 됩니다.

예를 들어, 소셜 로그인을 연동할 때, Google의 응답에 따라 회원가입이 성공/실패하는 경우가 있습니다.





이때, 데이터베이스에 관한 문제는 테스트 컨테이너(TestContainer), 를 통해 대부분 해결할 수 있습니다.

Testcontainers is an open source framework for providing throwaway, lightweight instances of databases, message brokers, web browsers, or just about anything that can run in a Docker container.





만약 외부 API를 호출하는 경우라면 간단하게는 목(Mock)을 사용할 수 있습니다. 조금 더 실 환경에 가깝게 테스트하고 싶다면, 외부 서버와 협력해서 테스트하고 싶다면 Wiremock을 사용할 수도 있습니다. AWS를 사용하고 있더라도 의존성을 제거할 방법이 존재하는데, 이 부분에 대해서는 별도로 학습해 보실 것을 권장해 드립니다.

Develop and test your AWS applications locally to reduce development time and increase product velocity. Reduce unnecessary AWS spend and remove the complexity and risk of maintaining AWS dev accounts.





이 외에도 도커 컴포즈 사용, 테스트 서버를 구축 등 다양한 방법이 있는데요, 각자가 처한 상황에 맞게 적절한 방법을 선택하시면 됩니다.

테스트 서버 구축 비용이 부담스럽다면 테라폼(Terraform)을 활용해보는 것도 좋습니다.







2-8. 서브 도메인 활용

서브 도메인을 활용해 자원의 의미를 명확하게 나눌 수 있습니다. 프로젝트에서 서버 비용 이슈로, 한 서버에서 8081 포트는 개발 서버를, 8080 포트는 실 서버를 사용하고 있습니다. 이를 구분할 때 서브 도메인을 활용한 것이죠. 자원이 의미 있는 이름 을 가질 수 있어 실수할 여지가 적었습니다.

image

API 문서도 서브 도메인으로 관리하는데, 신경 쓸 것들은 조금 늘어나지만 한 번 구축해 놓으면 편합니다.







2-9. JetBrain Http

JetBrain Http를 사용하면 기능을 빠르게 테스트 하고, 인수 테스트를 자동화 할 수 있습니다. API가 많을 경우, 정말 편리한데요, 이 경우 패키지를 나누고 별도의 관리 방법을 찾아야 합니다. 이는 별도의 포스팅을 할 예정이므로 자세한 설명은 생략하도록 하겠습니다.

1
2
3
4
5
6
7
8
### 1.연간 일정표 생성 ###
POST /api/monthly-tasks
Content-Type: application/json
Authorization: Bearer 

{
  "date": "2024-07-10"
}







2-10. 현재 내 상황 공유

현재 내 상황을 자주 공유하면 적절한 시기에 도움을 받을 수 있습니다. 어디까지 개발 했고, 어느 부분에서 막혔으며, 언제 쯤 개발될 것 같다 를 팀원이 알면 일정 조율도 쉬워지고요. 내가 한참 고민해도 풀릴까 말까 한 일이 다른 사람 설명 5분만에 해결되는 경우도 많잖아요? 적어도 팀원이 겪고 있는 문제를 보고 이를 도와주지 않는 사람은 없을 것입니다. 따라서 자신의 상황을 주기적/지속적으로 공유할 수 있도록 합니다.

이에 관한 좋은 영상이 있는데, 3분 23초 부터 4분까지 꼭 한 번 보세요.







3. 정리


프로젝트가 커지면 조그마한 기능 추가에도 애플리케이션이 실행되지 않거나 운영하기 힘든 경우가 발생하곤 합니다. 이를 잘 컨트롤하기 위해서는 어느정도 노하우가 필요한 것 같습니다. 앞으로도 운영을 위한 팁을 배우게 될텐데, 이 글을 계속해서 업데이트 해봐야 겠습니다.


This post is licensed under CC BY 4.0 by the author.

헬스체크 유예 기간으로 겪은 배포 이슈

ECS 롤링 배포 과정 중 겪은 이슈