트랙
/
Roc
Roc
/
연습 문제
/
은행 계좌
은행 계좌

은행 계좌

쉬움

소개

몇 년 동안 서류를 작성하고 기다린 끝에, 마침내 은행업 허가를 받았어요. 이제 공식적으로 자신만의 은행을 세울 자격이 생긴 거예요. 만세!

가장 먼저 할 일은 IT 시스템을 돌아가게 만드는 거예요. 하루 동안 열심히 일한 끝에, 이제 계좌를 열고 닫을 수 있고, 출금과 입금도 처리할 수 있어요.

테스트를 작성하기는 귀찮았던 터라, 친구 몇 명을 불러 시스템 테스트를 도와달라고 해요. 그런데 겨우 5분 만에 친구 한 명이 돈을 잃었다고 하네요! 작성한 Code에는 Bug가 없다고 확신하지만, 그래도 조사해 보려고 로그를 뒤져 보기 시작해요.

아, 역시나 예상대로 친구 잘못이었어요! 친구는 자신의 테스트 계정 정보를 다른 친구에게 공유했고, 둘은 짜고 같은 계좌에서 동시에 입금과 출금을 했던 거예요. 누가 이런 짓을 하겠어요?

누군가 자기 계좌에 동시에 접근하는 건 물리적으로 _불가능_하다고 따지지만, 친구는 은행 규정상 이걸 반드시 지원해야 한다고 능글맞게 알려 줘요. 그래서 동시 접근 지원이 없으면 서비스 오픈 신호도 없어요. 한숨을 쉬며, 내일 이걸 처리해야겠다고 머릿속에 메모해 둬요. 이러면 출시일이 최소한 하루는 더 밀리겠지만, 뭐...

지침

이번 과제에서는 계좌 개설과 해지, 출금, 입금을 지원하는 은행 계좌를 구현해요.

은행 계좌에는 인터넷, 휴대폰, 자동 결제 등 다양한 방식으로 접근할 수 있기 때문에, 은행 소프트웨어는 여러 스레드나 프로세스(용어는 사용하는 프로그래밍 언어에 따라 달라요)에서 계좌에 동시에 안전하게 접근할 수 있어야 해요. 예를 들어 입금과 출금이 동시에 여러 번 일어날 수 있는데, 잔액을 읽는 시점과 새 잔액을 설정하는 시점 사이에 경쟁 상태가 생기지 않도록 해야 해요.

계좌를 해지할 수 있어야 하고, 해지된 계좌에 대한 작업은 실패해야 해요.

Roc와 호스트의 역할

Roc 코드에서는 보통 비즈니스 로직에 집중하고, 동시성과 공유 상태는 플랫폼이 처리해요. 그래서 이 연습 문제에서는 동시성을 걱정할 필요 없이 BankAccount.roc에 계좌 연산만 구현하면 돼요. 계좌는 닫힌 상태로 시작하고, 다시 열면 잔액이 초기화되며, 입금과 출금 금액은 양수여야 해요.

서버 데모 (선택 사항)

함께 제공되는 bank-account-demo.roc는 구현한 코드를 작은 웹 서비스에서 어떻게 활용할 수 있는지 보여줘요. 편집기에서 읽어볼 수는 있지만, 테스트와는 별개이고 풀이의 일부도 아니에요.

데모를 실행하려면 먼저 컴퓨터에 Roc을 설치하고 Exercism CLI를 설치한 다음, 터미널에서 다음 명령을 실행해요:

exercism download --exercise=bank-account --track=roc

그다음 BankAccount.roc을 구현하고, 다음 명령으로 테스트해 봐요. CLI가 출력한 다운로드 위치가 아래 경로와 다르면 그에 맞게 바꿔 주세요:

cd ~/Exercism/roc/bank-account
roc test bank-account-test.roc

모든 테스트를 통과하면 다음 명령으로 데모 서버를 시작할 수 있어요:

roc bank-account-demo.roc

이 서버는 basic-webserver 플랫폼과 플랫폼에 내장된 SQLite 지원을 사용해요. Roc은 처음 실행할 때 플랫폼을 내려받아요. 별도의 데이터베이스 서버는 필요하지 않아요. 서버는 http://127.0.0.1:8000에서 요청을 기다리고, 현재 디렉터리에 bank-account-demo.db를 만들어요. 다른 데이터베이스 파일을 쓰고 싶다면 DB_PATH를 설정해요. 서버를 멈추려면 Ctrl-C를 눌러요. 데이터는 서버를 다시 시작해도 그대로 남아 있어요.

다른 터미널에서 curl로 이 웹 서비스를 호출할 수 있어요. Windows에서는 PowerShell의 curl 별칭을 피하려면 항상 curl.exe를 사용해요. 예를 들어 123번 계좌를 열고 50을 입금하고 10을 출금한 뒤 잔액을 확인하는 방법은 다음과 같아요:

curl -X POST http://127.0.0.1:8000/accounts/123/open
curl -X POST http://127.0.0.1:8000/accounts/123/deposit/50
curl -X POST http://127.0.0.1:8000/accounts/123/withdraw/10
curl http://127.0.0.1:8000/accounts/123/balance

잔액은 40이에요. 계좌 ID와 금액은 부호 없는 정수여야 해요. 갱신에 성공하면 OK를 반환하고, 실패하면 계좌 구현에서 만든 오류를 반환해요. 123번 계좌를 닫으려면 이렇게 해요:

curl -X POST http://127.0.0.1:8000/accounts/123/close

Linux/macOS에서 동시 요청을 시험해 보고 싶다면 새로운 계좌 ID를 쓰고, 클라이언트 여덟 개에서 입금 요청 100건을 보내 봐요:

curl -X POST http://127.0.0.1:8000/accounts/456/open
seq 100 | xargs -P 8 -I '{}' curl --fail --silent --show-error -X POST http://127.0.0.1:8000/accounts/456/deposit/1
curl http://127.0.0.1:8000/accounts/456/balance

Windows에서는 병렬 예제를 실행할 때 PowerShell 7+를 사용해요:

curl.exe -X POST http://127.0.0.1:8000/accounts/456/open
1..100 | ForEach-Object -Parallel { curl.exe --fail --silent --show-error -X POST http://127.0.0.1:8000/accounts/456/deposit/1 } -ThrottleLimit 8
curl.exe http://127.0.0.1:8000/accounts/456/balance

입금은 모두 성공해야 하고, 잔액은 100이어야 해요.

데모가 동시성을 처리하는 방법

각 요청은 계좌를 읽고, 작성한 Roc 함수를 호출한 뒤, 그 결과를 SQLite Immediate 트랜잭션 안에서 저장해요. 실패한 연산은 계좌를 바꾸지 않은 채 롤백돼요. SQLite는 한 번에 하나의 쓰기만 허용하기 때문에 동시 요청이 서로의 갱신을 덮어쓰지 못해요. 서로 다른 계좌의 경우에도 마찬가지예요.

참고: 이 연습 문제는 U64를 사용하지만, 데모는 잔액을 SQLite 정수로 저장하므로 I64 최댓값(9223372036854775807)을 넘는 갱신은 거부해요.

독립적인 계산을 병렬로 실행하는 예제를 보고 싶다면 parallel-letter-frequency 연습 문제를 풀어 봐요.

힌트

힌트 보기

match로 Closed와 Open(balance)를 구분해요. 연산은 전달받은 계좌를 수정하는 대신 갱신된 계좌를 반환해요. 갱신에 성공하면 Ok(updated_account)를, 연산이 거부되면 Err(...)를 사용해요.

GitHub에서 편집 링크가 새 창이나 탭에서 열려요
Roc Exercism

은행 계좌 문제를 시작해 볼 준비가 됐나요?

Exercism에 가입하고 Roc 트랙을 연습 문제 135개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.