Node.js - API Architecture (구조화)
코드 구조의 중요성
음... 저는 지금 express의 모든 메소드를 한 파일에 넣어놓았습니다. get 부터 delete 까지 전부다요. 지저분한 저의 js파일은 부끄럽게 짝이 없기 때문에 API 구조화를 해보려고 합니다.
당연히 한 파일안에 있으면 구현하기 간단합니다. 하지만... 저도 한 7~8개의 기능만 구현해 놓았는데 제가 어디에 무엇을 써놓았는지 알지 못하겠더라고요!
특히, 기업 시스템에서 하나의 파일에 모든 코드를 넣는 경우는 거의 없습니다. 기능적으로 영역을 구분하여 개별 코드를 관히하는 것이 좋습니다. 코드의 구조를 더 체계적으로 그리고 효율적으로 구현해 놓은 것을 코드의 아키텍처(architecture)라고 합니다.
코드를 어떤 기준으로 나누어 구조화해야 하는지 알아봅시다!
1. 확장성
모든 코드는 처음에는 조그마한 규모에서 시작합니다. 처음에는 간단한 시스템이라도 서비스가 발전할수록 시스템도 커져갈 수 밖에 없습니다. 그러므로 시스템을 구현할 때 확장성이 중요합니다. 확장성을 고려하지 않고 구현한 코드들은 시스템의 규모가 커질수록 문제가 많이 생길 확률이 높아집니다. 그러므로 확장성이 높은 구조로 코드를 구현하는 것이 중요합니다.
2. 재사용성
재사용성은 한 번 작성한 코드를 필요에 따라 다른 여러 곳에서 사용할 수 있는 특징을 말합니다. 재사용성이 높아야 코드의 양도 적어지고 개발의 속도도 높아지며, 코드 또한 더 안전하고 견고한 코드를 구현하기 쉽습니다. 코드 구조상의 재사용성은 일반 코드 레벨의 재사용성, 즉 함수나 클래스를 재사용하는 수준의 재사용성보다는 구조적인 재사용성을 이야기합니다.
3. 유지 보수 가능성
코드는 한 번 작성된다고 끝이 아니라, 여러 개발자와 여러 팀이 수정하고 유지 보수하게 됩니다. 따라서 유지 보수는 소프트웨어 지속 가능과 연결된 중요한 요소입니다. 유지 보수가 쉬운 코드를 구현하기 위해서는 구조적으로 로직이 잘 정리가 되고 나뉘어 있어야 합니다. 반대로 여러 로직이 함께 뒤엉켜 있는 코드일수록 유지 보수가 힘듭니다. 이러한 코드를 스파게티 코드 (spaghetti code) 라고 합니다. 스파게티처럼 뒤엉켜 있는 코드라는 뜻이죠. 그러므로 함수나 클래스 등을 사용하여 코드를 추상화(abstraction)하고 서로 독립적인 로직을 분리하여 필요한 곳에 적절하게 사용되도록 하는 코드를 구현하여 유지 보수를 더 쉽게 할 수 있도록 해야합니다.
4. 가독성
코드의 가독성이란 다른 개발자들이 코드를 읽었을 때 얼마나 이해하기 쉬운가를 나타내는 특징입니다. 어려운 로직일수록 더욱 가독성이 높게 구현해야 합니다. 어려운 로직을 어렵고 복잡하게 구현하는 것은 누구나 할 수 있지만, 어려운 로직을 쉽고 간단하게 구현하는 것을 할 줄 알아야만 좋은 개발자라고 할 수 있습니다. 마찬가지로, 코드의 구조 또한 이해하기 쉽게 구현해야합니다. 구조 자체가 복잡하게 구현된 소스 코드는 다른 개발자들이 이해하기가 힘들기 때문에, 유지 보수 또한 어려워질 수 밖에 없고 오류가 많이 생길 수 밖에 없습니다.
5. 테스트 가능성
코드는 작성된 이후로, 제대로 동작하는지 테스트가 이루어지게 됩니다. 로직이 간결하고 깔끔할수록 테스트를 진행하기에도 수월합니다. 테스트가 잘 구현되는 코드는 추상화가 잘 되어있고, 한 가지 역할만 하는 코드입니다. 코드의 구조도 마찬가지입니다. 추상화가 잘 구현되어 있고 담당하는 역할이 잘 나뉘어 있는 구조가 테스트하기 쉬운 구조입니다.
관심사 분리
역할이 분리되어 있지 않다는 것
API 서버를 레스토랑에 비유해봅시다. 손님(고객)이 요리를 주문(request)하면, 직원(server)은 냉장고(database)에서 재료(data)를 준비하고, 요리를 해 손님에게 요리를 서빙(response)합니다
그렇다면, 직원이 하는 일을 전부 나열해보겠습니다. 고객 맞이, 주문 받기, 메뉴 질문, 주문 확인, 재료 가공, 요리, 그리고 서빙까지. 사실은 직원이 하는 업무가 굉장히 많습니다. 레스토랑의 규모가 작을 때에는 (서비스의 규모가 작을 때에는) 혼자 모든 업무를 담당하는 것이 더 편합니다. 의사결정 속도가 빠르고, 일 처리를 간결하게 할 수 있죠. 그러나 식당이 조금 더 커지고, 고객이 많아진 상황에서는 한 사람이 하는 업무가 많고 복잡하면 다음과 같은 문제가 발생합니다.
- 직원의 역할이 명확하지 않아 실수할 확률이 높다.
- 실수의 원인을 파악하기 어렵다.
- 하고 있는 일이 많기 때문에 직원에게 문제가 발생해도 대체할 수 있는 자원을 찾기가 어렵다.
따라서 보다 효율적인 업무 처리를 위해서는 분업이 필요합니다. 고객을 응대하는 서버 직원, 요리를 전담하는 요리사 직원, 직원끼리의 소통을 책임지고 매니징을 하는 매니저 직원까지. 레스토랑의 업무가 효율적으로 그리고 혼선없이 진행되기 위한 역할 분리가 이루어진 것입니다.
직원1과 직원2, 직원 3의 역할을 명확히 분리하면 아래와 같은 이점을 얻을 수 있습니다.
- 확장성 각 직원이 맡은 역할에 집중하여, 레스토랑이 더욱 확장되어도 업무의 혼선이 적다.
- 직원1은 고객의 요청사항에 집중
- 직원2는 직원들의 의사소통에 집중하고, 식당 내부 과정을 파악
- 직원3은 냉장고의 상황과 요리에 집중
- 재사용성 기존에는 홀 주문만 받던 식당에 배달의 민족으로 주문도 가능하도록 새롭게 주문 기능을 추가한다고 생각해봅시다. 이때는 배달의 민족 주문만 확인해서 요리사에게 전달만 해주면 되기 때문에, 사람을 새롭게 채용할 필요 없이 기존에 주문을 확인하던 직원2가 역할을 동일하게 수행할 수 있습니다
- 유지 보수 가능성 외부 문제 상황 발생시 (고객의 잘못된 주문, 요리 재료의 손상) 문제 파악이 원활하며, 대응 속도가 빨라진다.
- 가독성 어떤 직원이 어떤 역할을 수행하고 있는지 파악이 수월하다.
- 테스트 가능성 각 역할의 수행 정도를 가늠하기 편리하다.
이렇게 알 수 있듯, 역할이 제대로 분리되어있지 않다는 것은, 하나의 시스템을 체계적으로 관리하기 어렵다는 것을 의미합니다. 따라서 역할에 따른 분리로 더 나은 시스템을 만들어갈 수 있습니다. 서버 코드 또한 역할에 따라 파일을 분리하여 보다 구조화(모듈화)된 시스템을 만들 수 있습니다.
역할에 따른 코드 분리
역할에 따라 코드를 분리하여 구조화를 진행합니다. 여기서 말하는 ‘구조'란 꼭 파일로서 분리하는 것은 아닐 수 있습니다. 프로그램 자체의 구조를 뜻하기도 합니다. 편의상 파일로 분리하는 과정을 진행하신 후, 점진적으로 소프트웨어 아키텍처 자체를 이해하는 것으로 폭을 넓혀가는 것이 좋습니다.
HTTP 통신을 처리하는 서버의 상황도 이와 크게 다르지 않습니다. 고객(손님)의 요청(주문)이 들어오면 서버는 데이터(재료)를 가공하여 요청에 알맞은 응답(요리)를 반환합니다.
서버가 하는 역할을 자세히 뜯어보면 굉장히 다양한 기능이 혼재되어 있습니다. 회원가입하는 로직을 다시 한 번 생각해봅시다.
- 클라이언트로부터 요청을 받는다.
- Request에 누락된 Key가 없는지 확인 하여 Key Error를 캐치한다.
- 비밀번호의 길이를 확인하고, 유저의 존재 유무를 확인한다.
- 비밀번호를 암호화한다.
- Database에 INSERT INTO 문을 전송하여 데이터를 저장한다.
- 제대로 데이터가 저장되었음을 응답으로 알려준다.
이러한 코드는 스파게티 코드로, 하나의 기능을 수정하거나 교체하려고 할 때 파일 내부의 모든 다른 코드들이 지장받을 가능성이 높습니다. 뿐만 아니라, 특정 파일에서 오류가 발생하면 해당 파일 내부에 있는 다른 코드들 또한 오류에 영향을 받을 수 있습니다. 코드가 조금만 많아져도 파일이 복잡해져 가독성이 떨어지는 문제도 발생합니다.
따라서 코드의 역할에 따라 파일을 분리할 수 있습니다. 크게 다음과 같은 기준으로 분리합니다.
- [파일 1] HTTP Request/Response 처리
- [파일 2] Business Logic 처리
- [파일 3] Database 통신 처리
- 파일 1은 Client로부터 받은 요청 자체에 관련된 일만 처리합니다.
- Request body 에서 데이터를 꺼낸다.
- JSON Response로 제품 정보를 전달한다.
- 예시
- 파일 2는 우리 서비스가 결정한 규칙에 따른 일들만 처리합니다.
- 비밀번호의 길이는 8자리 이상으로 규정한다.
- 이미 가입한 이메일로는 중복으로 가입할 수 없도록 한다.
- 비밀번호는 암호화해서 저장한다.
- 예시
- 파일 3은 데이터베이스와의 통신만 처리합니다.
- 데이터를 INSERT INTO로 저장한다.
- SELECT로 데이터를 가져온다.
- 예시
-
- 데이터를 INSERT INTO로 저장한다.
- SELECT로 데이터를 가져온다.
분리된 구조로 코드를 구현하면 코드의 확장성이 높아집니다. 각 파일에 포함되어 있는 코드의 목적이 명확하고 범위도 확실하기 때문에 코드의 구조를 파악하기도 쉽습니다. 또한, 각 파일이 서로 독립적이고 역할이 분명하므로 서로에게 끼치는 영향을 최소화하면서 확장하거나 수정할 수 있습니다.
이렇게 주된 역할과 집중해야할 기능을 분리하여 서로 다른 기능을 하는 파일은 신경을 쓰지 않도록 하는 것을 Seperation Of Concern, 관심사의 분리하고 합니다.
지금까지 코드의 구조화를 공부해봤는데요.
확장성, 재사용성, 유지보수 가능성, 가독성, 테스트 가능성의 사유로 매우 중요합니다.