원티드x위코드 프리온보딩 과제 수행간
개인적으로 흥미로웠던 점들을 정리하였습니다.
이번 과제는 기업 8percent에서 내주셨습니다.
과제와 관련 전반적 내용(README) 및 코드가 담긴 깃허브 주소는 다음과 같습니다.
🥕 과제 github 링크:
https://github.com/chrisYang256/8percent-assignment
이번 프로젝트에서 저의 담당 API는 signup과 signin이었습니다.
우리는 회원가입 시 user와 account 테이블 동시 생성 및 계좌번호 발급을
해주기로 설계하였고 저는 진짜 은행처럼 랜덤한 숫자 + 중복되지 않은
고유 번호의 계좌번호를 발급해보고 싶었습니다.
일단 너무 길게 생각하지 않고 코드로 관련된 무엇이든 만들기 시작하는게
경험상 저에게 맞기에 바로 키보드를 어루만지기 시작하였습니다.
일단 결과물은 다음과 같으며 중복 없는 데이터를 만들어낼 수 있었습니다.
def account_number():
def numbers():
number = random.randint(1000, 9999)
return number
account_number = f'{numbers()}-{numbers()}-{numbers()}-{numbers()}'
if not Account.objects.filter(number=account_number).exists():
return account_number
return account_number()
# 리턴값 예시: '9197-6853-7204-1115'
제작 과정은 다음과 같습니다.
구글에서 random.randint()
라는 함수를 찾아 1000~9999의 숫자를 랜덤하게
만들어낼 수 있었고 이것을 4번에 걸쳐 다른 결과가 나와야 했었습니다.
4회 동안 각각 다른 랜덤한 4자리의 수를 받기 위해 number1, number2...
와 같이
변수를 4개 만드는 것은 조금 웃겼기에 함수로 만들어 4번의 리턴을 받아
목적을 이룰 수 있었습니다.
그리고 중요한 것은 기존 발급된 계좌번호와 중복되지 않는 고유한 수의
계좌를 발급하는 것이었는데 뜬금 잘 알지도 못하는 재귀함수가 생각났습니다.
아마도 "자기 자신을 호출한다"는 특성이 생각나서였던 것 같습니다.
그래서 함수 안에 계좌생성 로직과 중복검사를 하는 if문을 넣은 후
DB데이터와 대조하여 생성한 계좌가 중복인 경우 다시 자기 자신을
호출하는 과정을 반복하도록 하여 마치 실재 은행처럼 중복이 없는 계좌번호를
발급할 수 있게 하였습니다!
단순한 로직이지만 참고자료 없이 상상한 내용을 정리하고 관련된 도구(랜덤함수,
재귀함수)를 떠올리고 찾고 알맞게 조립하여 구현해 냈기에 행복했습니다.🥲(감-동)
✔️ 주요 고려 사항은 다음과 같습니다.
계좌(accounts)는 여러개일 수 있기 때문에 테이블을 나누었습니다.
거래(transactions)는 저장되는 데이터의 구분 및 조회 성능을 고려하여
입금과 출금 테이블을 나누었습니다.
이 테이블의 약점
일반적으로 입출금을 동시에 조회하는 경우가 많고 pagination을 하기 때문에
조회성능은 크게 중요한 사항은 아니라고 판단됐습니다.
기간을 몇 년 단위로 조회하는 경우에도 입출금을 동시에 조회하는 경우
table join을 해야하기 때문에 입출금을 따로 조회하지 않은 이상 메리트가 없습니다.
또한 쓸데 없는 테이블 및 중복 column으로 인한 데이터 낭비가 있습니다.
가장 큰 패착으로는 transaction 테이블에 잔액(balance)이 없다는 점입니다.
accounts 테이블에 있는 잔액은 최종/현재의 잔액이고 거래 내용을 알 수 없고
거래내역을 조회하기 위해서는 transaction 테이블에 거래 후 잔액이 있어야 했습니다.
위 ERD처럼 현재 잔액과 거래당시 잔액 데이터를 따로 저장시켜야 합니다.
또한 앞서 말한대로 입출금 테이블은 특별한 정책이 있는 것이 아니면
따로 분리시키지 않는 것이 좋아보입니다.
그 외 거래형태를 구분하기 위한 transaction type 테이블을 별도로 두었습니다.
사실 코드를 구동해보기 전 까지 저를 포함하여 함께한 팀원들은
transaction 테이블의 balance 컬럼의 의미를 제대로 이해하지 못했습니다.
그저 잔액의 정확성을 위해 accounts의 balance와 비교하기위해
존재하는줄로만 생각하고 있었습니다.
하지만 거래내역을 조회하려다보니 거래 당시의 잔액에 대한 데이터가
없다는 것을 알고는 아차 하면서도 즐거웠던 기억이 납니다.
처음 "거래내역이 1억건을 넘어갈 때"라는 조건을 보았을 때
재미있게도 팀원 모두 조회 성능향상만을 생각했습니다.
그 결과중 하나가 아래와 같이 DataBase Indexing이었습니다.
class Transaction(models.Model):
amount = models.PositiveIntegerField()
balance_after_transaction = models.PositiveIntegerField()
sum_up = models.CharField(max_length=100)
account = models.ForeignKey(Account, on_delete=models.CASCADE)
transaction_type = models.ForeignKey(TransactionType, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'transactions'
indexes = [models.Index(fields=['account_id'])]
그러나 pagination을 사용하므로 데이터 로딩에 큰 차이가 없기도 했고
개인이 1억건의 거래내역을 조회할까? 라는 현실성 없는 상황을 배제하니
이것도 아닌거라는 결론이 팀원들 사이에 나오기 시작했습니다.
그러던 중 어느 팀원분이 database table partitioning이라는 개념을
찾으셨고 날짜 등을 기준으로 테이블을 나눌 수 있는 기능을 확인했습니다.
하지만 sqlite에는 해당기능 없음 + 프로젝트 마감 직전이었기에
우선 기존 코드를 잘 마무리하고 다음 프로젝트 때 시도하는 것으로
팀원 전부 동의하며 프로젝트를 마쳤습니다.
이번 과제를 하면서 테이블을 한 번에 만드는 것이 어렵다는 사실을
다시 한 번 마주쳤습니다.
물론 참고할 좋은 모델이 있다면 바로 모방할 수 있겠지만
아마도 나 자신의 창의력을 일부 막을 수 있다는 생각에
우선은 혼자만의 판단들로 테이블을 만들어보고는 합니다.
이번에는 참고한 모델을 보고도 API를 만들기 전 까지
참고하는 테이블의 의도를 완전히 알지 못했다는 점이 새롭기도 했는데
좌절같은 부정적인 감정이 아닌 "오호라?" 하는 흥미를 느끼는 스스로를 보며
약간은 더 긍적적으로 변하는 본인의 내면이 기분 좋기도 했습니다.
이런 긍정적 변화들이 앞으로도 자주 생겨서 저의 코딩생활과 인생 전반에
더 많은 행복을 가져다 주기를 바라게 되는 날입니다.😃