킴의 레포지토리

[원티드 프리온보딩 인턴십 백엔드6차] 4-2주차 wrap-up 및 현업에서 API를 개발할때 고려해야하는 11가지 세션 정리 본문

활동/원티드 프리온보딩 백엔드 인턴십

[원티드 프리온보딩 인턴십 백엔드6차] 4-2주차 wrap-up 및 현업에서 API를 개발할때 고려해야하는 11가지 세션 정리

킴벌리- 2023. 9. 16. 18:34

 한달간의 인턴십이 끝나고 마지막 세션에서는 그동안의 세션을 wrap-up하고 실무에 투입이 되었을때 주니어 개발자로서 현업에서 API를 개발할때 고려해야하는 11가지에 대해서 알아보는 시간을 가졌습니다.

 

 먼저, 이전 세션들을 복기하면서 세션별 중요했던 포인트들을 짚어주셨습니다. 이전 세션에 대한 글은 다음에서 확인해볼 수 있습니다.

 

 다음으로, 실무에 투입되었을때 주니어로서 고려해야할 11가지에 대해 짚어주셨습니다. 이 11가지 뿐 아니라 주니어 레벨을 벗어나면 더 고려해야할 것들이 많을 것이라고 말씀하셨습니다. 11가지를 스스로의 언어로 정리하면서 제가 실제로 프로젝트에 적용했던 방법에 대해 생각보았습니다.

 

1. project structure

 프로젝트의 체계적인 구조는 협업 및 코드의 유지 보수를 더욱 용이하게 만드므로 초기 단계부터 신중하게 설계하여야합니다. 프레임워크에서 어느정도 구조를 제공하지만, 개발자가 구체적인 구조를 결정하게 됩니다. 이때 좋은 프로젝트 구조는 다음을 고려하여 설계할 수 있습니다. 이미 프로젝트 구조가 결정된 상태라면 주니어 개발자는 프로젝트 구조 및 각 파일의 역할을 이해하여 기존의 코드를 적절하게 재사용하거나, 기존의 프로젝트 구조에 맞게 새로운 코드를 작성 수 있어야합니다.

 

- 모듈화: 기능이나 컴포넌트를 잘 정의된 모듈 또는 패키지로 분리하여야 코드의 재사용성을 높일 수 있습니다. 특히 모놀리스에서 마이크로 서비스 아키텍처 전환되는 추세인만큼 잘 분리하는 것이 결합도를 낮추고 응집도를 높이는데 중요하다고 생각합니다. 

 JWT를 사용해 사용자 인증 및 인가를 진행할때 jwt 관련 기능을 모듈화하였습니다, JWT를 생성하고, 검증하는 역할을 하는 JWTUtil 클래스를 만들고 로그인을 위한 필터인 LoginFilter와 인가가 필요한 요청을 검증하는 JWTCheckFilter에서 JWTUtil클래스를 재사용하였습니다. 또한 JWT와 관련된 클래스들을 jwt 패키지 안에 모아두어 어디서 어떻게 jwt관련 로직이 수행되는지 파악할 수 있도록 하였습니다.

 

- 책임 분리: 각 파일과 모듈은 하나의 책임을 가져야합니다. 

 JPA를 활용한 스프링 어플리케이션에서 entity 클래스의 값을 수정하는 역할은 entity가 담당하도록 하였습니다. service클래스는 entity클래스에서 필요한 함수를 요청하는 역할만 담당합니다. 도메인 데이터를 관리하는 책임은 entity에 응집되도록 하였습니다.

 

- 계층화: 디자인 패턴을 사용해 프로젝트를 계층화합니다. 보통의 레이어드패턴을 사용하여 Presentation Layer, Business Layer, Persistence Layer, Database Layer로 계층화합니다.

 

- 환경 설정 분리: 개발, 스테이징, 프로덕션과 같은 다양한 환경별 설정을 분리해야합니다. 

로컬, 개발, 프로덕션 환경으로 분리하고 프로파일을 분리할 수 있도록 각각 application-local.yml, application-dev.yml, application-real.yml 파일을 만들었습니다. 스프링 프로젝트 실행시 각 환경에 맞는 프로파일을 활성화시켜주었습니다. 이때 application-xxx.yml 파일은 github에 공개되는데 민감한 정보(datasource, secretkey 등)가 포함되지 않도록 민감 정보는 환경 변수로 등록하고 application-xxx.yml 파일이 환경변수를 읽어올 수 있도록 하였습니다.

 

- 의존성 관리: 필요한 라이브러리와 패키지 버전 정보를 저장하고, 주기적으로 업데이트를 확인합니다.

 스프링부트는 spring dependency management 플러그인이 버전을 자동으로 관리해주기 때문에 별도로 관리할 필요가 없습니다. 

 

- 테스트 가능성: 코드는 테스트가 가능하도록 작성되어야합니다. 유닛 테스트와 통합테스트를 고려하면서 코드를 작성할 수 있도록 합니다.

 스프링 mvc 프로젝트를 사용하면 계층이 자연스럽게 분리되어 계층별로 테스트할 수 있습니다. 스프링부트에서 제공하는 기능을 통해  controller/service/repository 계층을 나눠서 테스트하였습니다. controller는 @WebMvcTest를 사용해 presentation 계층 빈만 생성하여 service 빈을 @MockBean으로 만들어서 controller 계층을 서비스 계층과 분리해서 테스트 하였습니다. service와 repository는 @DataJpaTest를 통해서 비지니스 로직을 담당하는 부분만 빈을 생성해서 기능을 테스트하였습니다. 이후 @SpringBootTest를 통해 통합 테스트를 진행하였습니다.
 설정 파일은 JpaConfig, SecurityConfig, WEbConfig, S3Config 등 관심사별로 분리해서 작성해두고 테스트시에 필요한 설정 파일만 @Import에서 사용할 수 있도록 하였습니다.

 

- 확장성: 프로젝트가 변경과 확장에 유연하게 대응할 수 있도록 코드를 작성합니다. 정책이 바뀔때마다 한 부분만 수정하면 전체가 수정될 수 있도록 합니다.

 인증, 인가는 spring AOP를 바탕으로 하는 Spring Security 라이브러리를 사용해서 구현합니다. 이는 인증, 인가 관련 관심사를 비지니스 로직과 분리하는 역할을 합니다. 인증, 인가 로직이 추가되어도 비지니스 로직을 수정할 필요가 없습니다.
 validation은 여러 계층에서 진행하는 것이 아니라 요청 dto에서 한번만 수행합니다. validation 정책이 수정되어도 dto계층만 수정하면 되며, validation 로직이 여러 곳에 흩어져있지 않기 때문에 일부만 변경될 위험이 없습니다.
 정책이 변경될 수 있는 부분은 전략 패턴으로 구현하여 정책이 교체될 수 있도록 하였습니다.

 

- 보안: 보안 관련 베스트 프랙티스를 따릅니다. 비밀번호는 해시화하여 저장하고, 입력값은 항상 검증합니다.

 회원가입이나 로그인시 비밀번호는 해시값만 받도록 API를 구현하였습니다. 이를 통해 암호화되지 않은 비밀번호 web을 통해 전달되는 일이 없도록 하였습니다.

2. git status & git diff를 사용해 꼼꼼하게 검수한 내용을 커밋하자.

 커밋은 1)변경 사항을 추적할 수 있게 하고 2) 팀과의 협업을 원할하게 하고 3)장애 발생시 롤백 포인트가 됩니다. 따라서 커밋 하나하나를 꼼꼼하게 작성할 수 있어야합니다.

 이때 git status를 통해 불필요한 파일이 커밋에 포함되지 않도록 하고 git diff를 사용해 파일 하나하나 수정사항이 제대로 반영되었는지 불필요한 코드가 포함되어있지 않는지 꼼꼼히 확인하여야합니다.

 구현하기 전에 커밋할 단위를 미리 나눠보았습니다. 이때, 주로 구현할 기능별로 나눕니다. 이를 통해서 하나의 커밋에 여러 변경사항이 섞이지 않고 하나의 커밋은 하나의 수정사항만 반영되도록 신경쓰면서 개발할 수 있었습니다.
 또한 커밋할만한 기능이 구현되고 나면, 어플리케이션이 오류없이 잘 실행되는지 확인합니다. 변수명이 일부만 변경된다든가, 테스트 코드와 실행 코드가 일관되지 않다든가, 불필요한 주석이나 출력문이 없는지 확인합니다.
 이후, git add . 으로 전체 변경사항을 스테이징 하는 것이 아니라, 파일 하나씩 스테이징 하면서 정말 수정사항과 관련된 파일만 스테이징 되도록하고 불필요한 개인 설정 파일 등이 스테이징 되는 것을 방지하였습니다.

3. git commit message & pr message를 명확하게 작성하자.

메시지만 읽고도 다른 사람들이 코드 변경 사항이 무엇인지 쉽게 파악할 수 있도록 명확하게 작성하여야합니다.

커밋은 최대한 잘게 나누어서 하나의 수정사항만 반영하도록 하였습니다. 예를 들어, 사소한 변수명 수정이나 문서 수정 등이 다른 기능 수정사항과 섞여서 커밋되지 않도록 분리하였습니다.
 이때, commit 메시지는 tag를 refactor/test/feat/docs로 구분하여 커밋을 분류하였고, title에는 최대한 명확하게 body에 최대한 자세하게 수정사항을 기술하도록 하였습니다.
 pr message는 어떤 변경사항을 포함하는지, 왜 그렇게 구현했는지 기술하여 리뷰어들이 메시지를 읽고 어떤 코드를 집중해서 보아야할지 알려줄 수 있도록 하였습니다. 

4. 유지보수를 고려해 가독성 높은 코드를 작성하자.

코드는 개발자간의 의사소통 수단입니다. 동작하는 코드를 짜는 것보다 사람이 이해할 수 있는 코드를 작성하는 것이 중요합니다. 미래의 나, 다른 개발자들이 코드를 보았을때 쉽게 이해할 수 있어야, 코드를 재사용하거나 수정하는 등 유지보수가 용이해집니다.

함수명이나 변수명을 명확하게 작성하여 이름만 보고도 어떤 역할을 하는지 파악할 수 있도록 하였습니다. 만약 이름이 너무 길다면 함수가 너무 많은 기능을 담당하고 있지 않은지 체크해보았습니다.
함수는 하나의 기능만 담당하도록 하였고, 

5. 시스템과 관련된 기밀 사항을 안전하게 관리하자. 

한번이라도 커밋되면 히스토리에 남게 되고 되돌리기가 어려우니 커밋되지 않도록 방지하는 것이 중요합니다. 실무에서는 AWS KMS와 같은 보안에 특화된 솔루션을 사용하기도 합니다.

민감한 정보(datasource, secretkey 등)는 최대한 환경 변수로 등록하고 어플리케이션에서 환경변수를 읽을 수 있도록 합니다. 만약 그것이 어렵다면 민감한 정보를 담은 파일을 .gitignore에 추가하고 "git add ."을 해보며 스테이징 되지 않는 것을 확인합니다.

6. 서버와 실행 환경 분리를 고려해서 개발하자.

서비스의 안정성과 효율성을 위해서 서버의 실행환경을 dev/ staging/ production으로 분리하는 것이 좋습니다.

- dev: API 엔드포인트를 개발하는 과정에서 프론트엔드 개발자와 맞춰보는 용도. 코드가 빈번하게 수정되며 안정성은 보장되지 않음

- staging: 실제 사용자에게 서비스되기 전에 통합 테스트나 사용성 테스트 등 내부 QA 용도. 실제 서비스 환경과 거의 동일한 조건(인프라 구성, 서버 스펙 등)으로 구축하는 것이 좋으나, 실무에서는 비용 문제로 축소해서 구축하기도 함.

- production: 실제 사용자에게 서비스하는 환경으로 가장 안정성이 요구됨. 최종적으로 검증된 코드와 서비스만이 배포되어야함.

7. 서버와 데이터베이스에 대한 접근을 제어하자.

정보가 유출되지 않도록 자원에 대한 엄격한 접근 제어가 필요합니다. production 환경의 서버와 db는 전체 개발자에게 권한을 주지 않고 일부 관리자만 접근할 수 있도록 하는 것이 좋습니다. 이때 외부 접근(일반적인 인터넷을 통한 접근)은 원천 차단하고, VPN을 사용하여 물리적으로 외부에 위치하더라도 네트워크 상에서는 내부에 연결된 것처럼 접근할 수 있도록 합니다.

 AWS EC2와 RDS를 사용할때 보안그룹을 사용해 http나 https요청은 누구나 가능하지만 ssh를 통해 서버에 직접 접근하는 것은 특정 IP만 허용하도록 하였습니다. 또한 RDS는 연결된 EC2에서만 접근할 수 있도록 하여 DB 데이터를 보호할 수 있도록 하였습니다.

8. 스키마 구조가 변경되는 상황을 대비해 데이터베이스도 형상 관리하자.

데이터베이스 스키마가 변경되는 경우가 있는데 이때 데이터베이스 스키마도 형상 관리가 필요합니다. flyway는 오픈소스의 db 마이그레이션 도구로 SQL로 데이터베이스 구조를 관리하고 소스코드와 데이터베이스 관리 코드를 분리할 수 있도록 합니다. 

9. 동시 사용자가 많은 경우를 고려해서 부하테스트하고 API 품질을 높이자.

jmeter나 nGrinder와 같은 부하테스트 도구를 사용해서 동시에 많은 사용자가 접속해도 어플리케이션이 감당할 수 있는지 테스트합니다. 권장되는 응답속도는 어플리케이션의 특성에 따라 다르지만 보통 500ms 이내 최대 700ms입니다.

  이때 클라우드를 사용한다면 외부에서 클라우드에 요청을 보내서 부하테스트를 하는 것이 아니라 서버 내부에서 부하테스트를 진행합니다. 트래픽이 많이 발생하여 높은 비용이 청구될 수 있기 때문입니다. 

10. 테스트 문화를 만들자.

효과적인 테스트 전략은 문제를 빠르게 찾을 수 있게합니다. 테스트 코드를 작성하는 문화를 만들어갈 수 있도록 합니다. 이때 단위 테스트를 일일이 짜기 어려운 상황이라면 최소한 통합 테스트라도 작성할 수 있도록 합니다.

- 단위 테스트: 기능 중심으로 테스트하는 것으로 보통 하나의 함수가 대상이 됨.

- 통합 테스트: 어플리케이션을 실행시켜서 실제로 http요청하는 방식으로 테스트. postman, httpie 등 클라이언트 도구로 동작을 확인.

- 종단간 테스트 UI 테스트: 실제 사용자가 서비스를 사용하는 경로를 테스트. 배포 전 필수적이지만 비용이 크므로 전체 테스트의 10 % 정도의 비중으로 테스트한다.

11. 내가 운영중인 시스템의 상태를 모니터링하자.

 시스템 부하가 많이 걸리면 응답시간이 늘어나고, 장애가 발생할 가능성이 높고, 클라우드 비용이 증가합니다. 따라서 주기적으로 시스템을 모니터링하여 필요 이상으로 자원을 사용하는 서비스가 없는지 파악하는 것이 중요합니다. 

 프로메테우스나 그라파나와 같은 유료 모니터링 시스템을 사용할 수도 있지만, 서버에 직접 접속해서 기본적인 부하 상태를 확인할 수 있도록 합니다. 

<웹 개발자를 위한 대규모 서비스를 지탱하는 기술> 책을 참고하여 서버 시스템을 모니터링 하였습니다.
1) "top", "htop"혹은 "uptime" 명령어를 사용해 시스템의 Load Average를 살펴봅니다.
2) 만약 Load Average가 높다면, "sar"명령어를 사용해 그 원인이 CPU 부하인지 I/O 부하인지 파악합니다. 
3) CPU 부하가 높다면 "top", "sar", "ps"로 프로세스의 상태나 CPU 사용시간등을 확인해 어떤 프로그램이 원인인지 확인합니다.
4) I/O 대기율이 높다면 프로그램으로부터 입출력이 많아서인지, 메모리 부족으로 스왑이 발생해서인지 확인합니다.