- 진행 날짜 - 2021.11.18 pm 17:00 ~ 2021.11.22 pm 14:00
- 과제 필수 포함 사항
주요 평가 사항
- 주어진 정보를 기술적으로 설계하고 구현할 수 있는 역량
- 확장성을 고려한 시스템 설계 및 구현
과제 안내
디어는 사용자의 요금을 계산하기 위해 다양한 상황을 고려합니다.
- 우선 지역별로 다양한 요금제를 적용하고 있습니다. 예를 들어 건대에서 이용하는 유저는 기본요금 790원에 분당요금 150원, 여수에서 이용하는 유저는 기본요금 300원에 분당요금 70원으로 적용됩니다.
- 할인 조건도 있습니다. 사용자가 파킹존에서 반납하는 경우 요금의 30%를 할인해주며, 사용자가 마지막 이용으로부터 30분 이내에 다시 이용하면 기본요금을 면제해줍니다.
- 벌금 조건도 있습니다. 사용자가 지역 바깥에 반납한 경우 얼마나 멀리 떨어져있는지 거리에 비례하는 벌금을 부과하며, 반납 금지로 지정된 구역에 반납하면 6,000원의 벌금을 요금에 추과로 부과합니다.
- 예외도 있는데, 킥보드가 고장나서 정상적인 이용을 못하는 경우의 유저들을 배려하여 1분 이내의 이용에는 요금을 청구하지 않고 있습니다.
최근에 다양한 할인과 벌금을 사용하여 지자체와 협력하는 경우가 점점 많아지고 있어 요금제에 새로운 할인/벌금 조건을 추가하는 일을 쉽게 만드려고 합니다. 어떻게 하면 앞으로 발생할 수 있는 다양한 할인과 벌금 조건을 기존의 요금제에 쉽게 추가할 수 있는 소프트웨어를 만들 수 있을까요?
우선은 사용자의 이용에 관한 정보를 알려주면 현재의 요금 정책에 따라 요금을 계산해주는 API를 만들어주세요. 그다음은, 기능을 유지한 채로 새로운 할인이나 벌금 조건이 쉽게 추가될 수 있게 코드를 개선하여 최종 코드를 만들어주세요.
다음과 같은 정보들이 도움이 될 것 같아요.
- 요금제가 사용자 입장에서 합리적이고 이해가 쉬운 요금제라면 좋을 것 같아요.
- 앞으로도 할인과 벌금 조건은 새로운 조건이 굉장히 많이 추가되거나 변경될 것 같아요.
- 가장 최근의 할인/벌금 조건의 변경은 '특정 킥보드는 파킹존에 반납하면 무조건 무료' 였습니다.
- 13팀 과제 Github 리포지토리
🏫 사용한 프레임워크 & 라이브러리
- Nest JS
- config
- supertest
- typeorm sqlite3
- class-validator & class-transformer
- passport-locacl & passport-jwt
💯 구현 목록
- 킥보드 이용료 계산
- ✅ 현재의 요금 정책에 따른 요금 계산 API
- 기본 요금 계산
- ✅ 지역별 기본 요금 + 분당 요금
- 예외 조건 계산
- ✅ 킥보드 고장으로 이용 못할 경우 (이용시간 1분 이내 시 요금 청구 X)
- 할인 조건 계산
- ✅ 마지막 이용으로부터 30분 이내 재이용시 기본요금 면제
- ✅ 킥보드 파킹존 반납 시 30% 요금 할인
- 벌금 조건 계산
- ✅ 파킹 금지 구역에 반납 시 6,000원 벌금 추가
- ✅ 사용 지역에서 벗어나 다른 지역에서 반납할 경우 거리에 비례하여 벌금 부과
- ✅ 사용자의 위치 정보 전달
- ✅ 사용자의 위치 정보 전달
- 테스트 코드
- ✅ e2e Test
📋 Database Modeling
💭 Project Review
프리온보딩 백엔드 코스 6차 과제로 스마트 모빌리티 사업을 하고 있는 디어라는 기업의 과제를 진행하였습니다. 이번 과제는 요금만 받아오는 API만 만들면 끝이었으나, 해당 API는 변동사항이 심하기 때문에 그것을 고려하여 짜야했습니다. 그리고 해당 기업이 아무래도 스마트 모빌리티 사업을 하고 있었기 때문에 위치 기반 쿼리를 사용해야 했습니다. 이 부분 때문에 난이도가 확 높아져서 요번 과제는 토요일 오전 10시까지 제출하는 것이 아니라 월요일 오후 14시까지 제출해도 됐습니다. 그래서 팀원들과 함께 요번에는 구조를 잘 잡아서 나름 높은 퀄리티의 과제를 제출하려고 했습니다.
슬프게도 잘 안됐지만요......
💬 데이터 베이스 설계
해당 기업에서는 사용자의 요금을 계산하기 위해 다양한 상황을 고려합니다. 지역별로 다양한 요금제를 고려하고 있으며, 할인 조건, 벌금 조건, 예외 조건이 상황에 따라 계속 변경됩니다. 때문에 이러한 조건들이 변경될 때마다 코드를 바꾸는 것은 낭비이기 때문에 최대한 적게 만들기 위해 이러한 조건 사항들을 데이터베이스에 저장해서 쉽게 계산할 수 있도록 해보고 싶었습니다. 그러면 로직의 변경을 최소화시킬 수 있을 것 같았거든요. 그래서 팀원들과 함께 이런 방식으로 데이터베이스를 설계해보기로 하였습니다.
우선, 사용자들이 전동 킥보드를 사용한다는 가정하에 사용자 테이블과 킥보드 테이블을 만들었습니다. 그리고 사용자가 킥보드 사용내역을 쉽게 볼 수 있게 내역 테이블을 만들었습니다. 저희는 해당 서비스를 가입한 지역에서 여러대의 킥보드를 관리하고 있다고 보고 아래의 그림처럼 관계를 맺었습니다. area_coords는 지역 테이블의 경계를 표시하는 위도와 경도로 이루어진 점의 데이터가 들어갑니다. 그러니까 area_coords의 데이터를 보면 해당 지역의 경계 모양을 그릴 수 있는 것이죠.
그런 다음 지역에 존재하는 금지구역과 파킹존 구현을 위해 1:N관계로 지역 테이블과 연결해 두었습니다. 지역별 금액이나 분당 요금의 경우에는 지역마다 다르기 때문에 1:1 관계로 area_policies라는 테이블을 만들어서 지역 테이블과 연결하였습니다. 이런 식으로 테이블을 설계하면 지역마다 파킹존이 바뀌는 경우나 금지구역이 바뀌는 경우에도 유연하게 대처할 수 있을 것이라고 생각하였습니다. 또한 변동성이 높은 지역별 금액이나 분당 요금의 경우에도 처리할 수 있을 것이라고 생각했죠.
여기까지는 괜찮다고 생각했는데, 문제는 금액을 처리하기 위해 만들어둔 테이블이었습니다. 처음에는 변동사항이 심한 할인, 벌금, 예외 조건을 처리해보기 위해 각각 discounts, exceptions, penalties라는 테이블과 내역 테이블을 만들고 사용자 킥보드 내역 테이블과 1:N으로 연결을 하였습니다. 공통 코드 테이블도 만들어서 각각의 조건들을 위한 코드 값들도 넣어두었습니다. 이렇게 짜면 쿼리에서 변동이 심한 조건들에 따라 쉽게 요금을 계산할 수 있을 것이라 생각하였으나 그렇지 않았습니다.
금액 계산을 손쉽게 하기 위해 이러한 구조로 설계를 하였는데, 나중에 쿼리를 짜보니 어떤 조건에 해당 코드가 쓰여야 할지 데이터베이스가 판단을 할 수가 없어서 제대로 작동하지 않았습니다. 또한 관계가 제대로 맺어져 있지 않아서 내가 원하는 조건의 코드를 사용할 수 없었습니다. 그래서 어쩔 수 없이 데이터베이스 구조를 바꿔보았지만, ERD설계 경험이 많지 않아서 요금 계산을 위한 데이터베이스를 설계할 때 이런 식으로 설계하는지는 잘 모르겠습니다 ㅠㅠ. 이 부분에 대해서는 조금 더 많은 의견을 나눠봐야 할 것 같습니다.
💬 클래스 vs 메소드
데이터베이스에서 계산로직을 처리하는 것이 힘들어져서 요번에는 코드로 구현해보기로 했습니다. 여기서 재밌는 토론을 했는데요. 해당 계산 로직을 클래스로 두는 것이 좋은가 메소드로 두는 것이 좋은가에 대한 것이었습니다.
클래스 방식의 장점은 각 조건들을 다른 클래스로 분리함으로서 특정 조건의 변경이 이뤄지는 경우 해당 클래스 파일만 수정하면 되므로 독립성이 높아집니다. 하지만 할인 종류를 추가할 때마다 Nest js 할인 모듈에 생성된 클래스의 의존성 주입을 관리해줘야 합니다.
메소드 방식의 장점은 각 조건별 동일한 역할을 가진 메소드들을 한대 모아서 관리할 수 있게 됩니다. 일관성 있는 함수들을 하나의 클래스에 모아 관리하는 것이 유지보수에 유리할 수 있습니다. 조건이 추가될 경우 의존성 주입 없이 새로운 메소드를 추가해주면 됩니다. 다만, 조건 모임의 로직들이 길어질 경우에는 하나의 클래스가 엄청나게 거대해질 수 있습니다.
저는 클래스는 어플리케이션 내에서 객체를 생성하거나 인스턴스화 하기 위한 것이고, 메소드는 객체의 동작을 나타내는 함수라고 이해하고 있습니다. 그래서 객체지향 개발을 하기 위해서는 클래스로 만들어서 해야 한다고 생각했는데, 이런 계산 로직들은 내부 로직이 의외로 단순하기 때문에 비교적 크기가 큰 클래스를 만들기보다는 메소드화 시켜서 관리하는 방법도 나쁘지 않을 것 같다는 생각이 들었습니다.
아직 이러한 고민을 많이 해보지 않아서 무엇이 정답이라고 확실히 판단할 수 없었습니다. 팀내에서 투표를 한 결과 메소드 방식을 선택해서 구현하기로 정해졌습니다. 이번 과제에서 가장 핵심적인 부분은 새로운 조건이 추가되거나 수정될 때 쉽게 수정할 수 있도록 코드를 구현하는 것이었기 때문에 클래스보다는 조금 더 간편한 메소드 방식을 선택하기로 한 것이지요. 그래서 이번 과제는 메소드 방식을 사용하여 해당 과제를 완성했습니다.
👩💻반성점 OR 다음 프로젝트부터 고치고 싶은 것
아쉽게도 이번 프로젝트는 과제를 제대로 마무리하지 못하고 제출하게 되었습니다. 기한이 월요일까지로 길게 주시긴 하셨으나, 미리 공지된 사항이 아니라서 팀원들 전부에게 다른 일정이 있는 상태로 과제를 수행하게 됐습니다. 그래서 저희 팀 같은 경우에는 토요일 까지 마무리해야 했지만 슬프게도 데이터베이스 설계에서 실수하는 바람에 생각했던 코딩 일정이 밀리게 됐습니다. 어떻게든 완성을 해서 제출하긴 했지만 요금 계산 로직에 하드 코딩된 부분들이 많았습니다. 제출한 이후에도 다른 팀원분이 고치긴 하셨지만 여전히 하드 코딩된 부분이 있습니다. 아직까지는 경험도 부족하고 객체지향에 대한 이해가 부족하다보니 이런 결과가 나온것 같습니다.
하지만 이번 과제를 하면서 객체지향 개발을 어떻게 해야 하는가에 대해 생각을 할 수 있게 됐습니다. 그전까지는 당연하다고 생각했던 것들이었는데 다른 사람과 의견을 나눔으로서 새로운 시각을 갖게 될 수 있었습니다. 객체지향에 대한 고민을 더 해보고 더 나은 방법을 찾으려는 시도를 꾸준히 해봐야 할 것 같습니다.
'프리온보딩 백엔드 > TIL(Today I Learned)' 카테고리의 다른 글
[Assignment 7] Cardoc(카닥) TIL (0) | 2021.11.30 |
---|---|
[Assignment 5] Humanscape(휴먼스케이프) TIL (0) | 2021.11.18 |
[Assignment 4] 8PERCENT(8퍼센트) TIL (0) | 2021.11.14 |
[Assignment 3] RED BRICK(레드브릭) TIL (0) | 2021.11.11 |
[Assignment 2] MAPIA COMPANY(마피아 컴퍼니) TIL (0) | 2021.11.07 |