설계란?
- 프로그래밍에서의 코드 설계는 코드와 파일, 그리고 폴더 구조를 설계하는 것을 말합 니다.
만약 적절한 설계를 하지 않고 코드를 작성하게 될 경우 여러가지 문제가 발생할 수 있습니다.
코드가 몇백 줄(혹은 몇천 줄)이 되기 때문에, 수정할 코드가 있을 때 해당 부분을 찾기 힘듭니다. 따라서 유지보수가 어려워집니다.
코드와 코드 사이 (함수와 함수 사이 등) 어떤 관계가 있는지 한 눈에 알아보기 힘듭니다.
각 코드(함수, 객체 등)의 역할과 기능이 명확하게 구분되어 있지 않으므로, 기능별로 테스트(유닛 테스트)를 진행하는 것이 어려워집니다.
하나의 파일을 동시에 여러 사람이 수정하는 것은 힘들기 때문에, 분업이 어려워 집니다.
새로운 기능을 추가하고자 할 때, 기존에 존재하는 코드의 어느 부분을 수정하고 혹은 새로 작성해야 하는지 알아내기 힘듭니다. 즉, 확장성이 부족한 코드가 됩니다.
위 문제들을 해결하기 위해 다양한 코드 구조 설계가 만들어졌는데, 저는 그 중 3계층 구조 설계를 사용하기로 했습니다!
3계층 구조 설계란 3개 구조로 나누어 설계하는 것을 말합니다.
Control layer (컨트롤러): 사용자의 요청(request)을 분석한 후, 알맞은 서비스로 해당 요청을 전달해 준 다음, 서비스의 결과를 다시 응답(response)하는 층입니다.
즉, 라우팅(Routing)이 이루어지는 층입니다.
express의 경우,req.params(), req.body(), res.status(), res.send()와 같은 코드가 작성됩니다.
Service layer(서비스): 컨트롤러로부터 전달된 요청에 로직을 적용하는 층입니다.
이 때 로직이라는 것은, 예를 들어 로그인 서비스의 경우 다음과 같습니다.
아래와 같은 로직을 if-else 등의 코드로 구현할 수 있습니다.
Model layer(데이터): 서비스 층에서 데이터베이스 접근이 필요한 경우가 있는데, 이 때 데이터 관련 코드가 작성되는 층입니다. Mongoose의 경우 Model.find({})와 같은 코드가 작성됩니다.
로그인 서비스를 예시로 보면, 요청으로 온 ID가 데이터베이스에 존재하는지, 만약 존재한다면 그 때 데이터베이스 상에 저장된 비밀번호는 요청으로 온 (사용자가 입력한) 비밀번호와 일치하는지 확인해야 합니다.
이 때 서비스 층은 데이터 층에 데이터베이스 확인 요청을 하게 되는 것입니다.
위와 같이 코드를 역할별로 분리하여 각 층에 구현하는 경우, 다음과 같은 장점이 있습니다.
각 역할별로 개발 업무를 분담할 수 있으므로 분업이 용이해집니다.
역할 별로가 아닌 MVP 별로 개발 업무를 분담하는 경우에도, 각 MVP가 어떤 흐름으로 구현되는지 (Controller
에서 사용자로부터 요청을 받고, 요청에 대해 Service
층에서 로직을 구현하고, 필요 시 데이터베이스에 접근한 후, 결과를 Controller
가 응답합니다) 코드 구조를 보고 이해하기 쉬워집니다.
라우팅 관련 코드는 Controller
폴더에서, 서비스 로직 관련 코드는 Service
폴더에서, 데이터 관련 코드는 Model
폴더에서 구분하여 확인할 수 있으므로, 필요한 코드를 빠르게 찾을 수 있으며, 따라서 유지보수가 용이해집니다.
웹 서비스의 구현 방식을 변경하고자 할 때, 예를 들어 데이터베이스를 Mongodb
에서 Mysql
로 변경하고자 할 때, 각 폴더는 역할이 분리되어 있으므로, Model
폴더 코드만 변경하면 되고 나머지 Controller
, Service
폴더는 변경하지 않아도 됩니다. 유지보수가 용이해집니다.
코드가 기능별로 구분되어 있으므로, 기능별로 테스트(유닛 테스트)를 진행하기 용이해집니다.
그래서 이런 3계층 구조 설계를 사용하기 위해 현재 폴더구조를 아래와 같이 설정했습니다.
└───src
├───db # Model layer
│ ├───models
│ └───schemas
├───middlewares
├───routers # Control layer
└───services # Service layer
처음 설계할 때 User DB에 배열로 수상 내역을 넣는 식으로 진행 했으나 Award 부분 관리를 하는데 User DB 부분을 같이 봐야하는 점이 불편해서 User Model 과 Award Model을 따로 나눈 후 Award 모델에서 User모델을 참조하게 하여 진행하였습니다.
import mongoose from "mongoose"; const Schema = mongoose.Schema; const model = mongoose.model; const AwardSchema = new Schema({ id: { type: String, unique: true, required: true, }, title: { type: String, required: true, }, description: String, author: { type: Schema.Types.ObjectId, ref: "User", required: true, }, }); const AwardModel = model("Award", AwardSchema); export { AwardSchema, AwardModel };
그리고 담당한 다른 파트인 Education [학력 부분 API ] 의 모델도 Award Model 처럼 설정해주었습니다.
Gitlab 이슈를 생성 하게 되면 이슈 번호가 지정되는데, Commit 을 할 때 특정 명령어와 이 이슈번호를 사용하면 Commit을 할 때 이슈를 자동으로 닫게 할 수 있습니다.
만약 이슈 번호가 #79번 일때, "close #79"
또는 "fix #79"
처럼 입력하게 된다면 commit 을 하면 이슈도 자동으로 닫히게 됩니다.
만약 다른 방식으로 닫게 하고 싶거나 추가하고 싶다면 config/gitlab.yml
의 issue_closing_pattern
에 지정하면 됩니다.
DB 설계를 그냥 머리에서 떠오르는 대로 시작했더니 결국 DB를 다시 뜯어고치는 일이 생겼다.
처음부터 DB 설계를 짜고, 진행할 일 들을 미리 구상했으면 뜯어고치는 일을 없애거나 최소화 할 수 있을 것 같아 다음번엔 꼭 진행할 예정에 맞춰 설계를 한 후, 스키마를 작성해야겠다.
그리고 이슈를 작성하고, commit 하고, merge 한 후, 이슈를 직접 닫아 줬었는데 GitLab에서 작성한 이슈를 commit 으로 자동으로 닫게 할 수 있다는걸 알고 역시 아는 만큼 편하고 효율성 높게 작업할 수 있다는 걸 다시 한번 느껴 내가 사용하는 툴 들의 기능을 한번 더 공부해봐야겠다.
처음 3계층 구조 설계로 폴더를 나눈 후 코드를 작성하는데 여기서 작성하고 저기서 작성하고 처음에는 헷갈리고 힘들었지만, 적응하고 나니 내가 원하는 부분을 편집할 때 딱 그 부분만 가서 수정하면 되는 것을 몸으로 느끼자 평소에 한 곳에 짜는 것 보다 훨씬 편하게 느껴졌다.
앞으로 다른 프로젝트를 진행할 때는 다른 구조도 사용해봐야겠다.