- 핀테크가 계좌 개설·결제·온보딩을 직접 은행 API처럼 붙일 수 있게 하는 banking-as-a-service 플랫폼이며, 2023년 3월 영국 은행 라이선스를 받아 규제 은행이 됨
- 시스템은 Clojure on Kubernetes on AWS 위에서 동작하고, 대부분의 입력을 이벤트로 바꾸는 이벤트 소싱 구조와 FoundationDB 저장소를 결합함
- FoundationDB는 strict-serializable key-value store로 트랜잭션과 동시 쓰기를 지원하며, Griffin은 Datascript를 포팅한 Datomic 유사 계층으로 원자적 읽기·쓰기를 구성함
- 비즈니스 로직은 Clojure map을 받아 Clojure map을 내보내는 작은 log processor 중심으로 분리하고, 외부 시스템 접근은 프로토콜과 전용 proc로 제한함
- 클로저의 불변성과 감사 로그 친화성이 금융 서비스 요구에 맞고, 원격 채용과 결합하면 작은 후보군에서도 높은 품질의 엔지니어를 찾기 쉽다고 봄
API로 제공하는 규제 은행 플랫폼
- Griffin은 핀테크 기업이 은행 기능을 빠르고 안전하게 통합하도록 돕는 banking-as-a-service 플랫폼임
- 2023년 3월 Financial Conduct Authority로부터 UK banking license를 받아 완전히 규제되는 영국 은행이 됨
- Griffin은 자신을 “the bank you can build on”이라고 부르며, 은행을 위한 AWS 같은 기반을 목표로 함
- 고객 온보딩 API
- 은행 계좌 생성 API
- 결제 API
- 핀테크가 이런 기능을 제공하려면 법적으로 은행과 협력해야 하며, 현재는 메인프레임을 쓰는 기존 high street bank와 일하는 경우가 많음
- Griffin은 은행 라이선스와 기술 플랫폼을 함께 제공해, 미래 핀테크가 그 위에 서비스를 구축할 수 있는 기반이 되려 함
- 라이선스는 받았지만 당시에는 mobilization 단계에 있었고, 감사 완료·추가 자금 조달·코드 작성 완료 후 이 단계가 끝남
- 목표 시점은 해당 연도 Q3 또는 Q4였음
클로저를 선택한 배경
- 클로저는 불변성, 표현력, 감사 로그가 필요한 금융 서비스와의 적합성 때문에 플랫폼 언어로 선택됨
- Allen Rohner는 2007년경 Rich Hickey의 Clojure 발표를 보고, 자신이 만들던 Lisp보다 더 낫다고 판단함
- 2011년 CircleCI를 창업한 뒤 클로저를 장기간 사용했고, CircleCI에서도 잘 작동했으며 금융 서비스에도 맞는다고 봄
- JVM에 대해서는 처음 몇 년간 장점을 충분히 인식하지 못했지만, 이후 큰 이점으로 평가가 바뀜
- 다른 niche startup language는 라이브러리 부족, 컴파일러·런타임 성능 문제를 겪을 수 있음
- JVM은 이런 위험을 줄이는 기반이 됨
- 언어 선택은 회사의 성격을 드러내며, Python이나 Java보다 클로저가 더 강력한 선택이라고 판단함
- niche language를 쓰면 후보 수는 줄어도 고급 인재 비율이 높아질 수 있다고 봄
FoundationDB로 만든 데이터 계층
- Griffin 아키텍처는 Clojure, Kubernetes, AWS 위에서 동작하며 거의 전부 이벤트 소싱으로 구성됨
- 데이터베이스는 FoundationDB를 사용함
- FoundationDB는 트랜잭션을 지원하는 strict-serializable key-value store임
- Silicon Valley 스타트업으로 시작함
- 2015년 Apple에 인수됨
- 2018년경 Apple이 다시 오픈소스로 공개함
- Apple은 iCloud 프로덕션에서 사용함
- Apple은 FoundationDB가 초당 약 100만 트랜잭션으로 동작하는 벤치마크를 수행함
- strict serializable은 데이터베이스 정합성에서 가장 높은 수준에 해당함
- 기본 API는
get a key,set a key에 가까우며 SQL이 아님 - Griffin은 Datascript를 FoundationDB에 포팅해 Datomic 유사 계층을 구성함
- strict-serializable 데이터 저장소 위에서 원자적 쿼리가 가능함
- 트랜잭션 기반 읽기와 쓰기를 지원함
- FoundationDB는 single writer가 아니라 concurrent writes를 지원함
- Griffin은 초당 1,000개 이상의 트랜잭션이 필요하며, 이 요구를 만족하고 있음
이벤트 소싱과 log processor
- Griffin 시스템의 모든 입력은 이벤트가 됨
- API request
- 서드파티 webhook
- 이벤트는 message log에 들어가며, Griffin에서 이벤트는
type필드와 key/value, spec을 가진 Clojure map임 - 전체 시스템은 이벤트에 대한 반응으로 구성됨
- 작은 log processor를
proc라고 부름- proc는 “message type A를 듣고, 반응으로 B 또는 C를 emit한다”는 식으로 동작함
- 각 proc는 자체 private state를 가짐
- 메시지 흐름은 그래프로 구성할 수 있고, 이벤트는 terminal node에 도달할 때까지 이동함
- 예를 들어 웹 서버는 HTTP 이벤트를 받고 결제 요청을 기록한 뒤,
payment created또는payment rejected이벤트를 기다렸다가 클라이언트에 응답함- 이 흐름은 Netty asynchronous HTTP handler를 사용함
- 모든 이벤트는 FoundationDB에 기록됨
- 각 log processor는 FoundationDB 안의 자체 namespace 같은 private data를 가짐
- proc는 특정 이벤트 타입이 기록되는 것을 감시하고, 자신의 이벤트를 FoundationDB에 다시 기록함
- FoundationDB는 DB key 변경 감시 기능을 제공해 reactive system을 효율적으로 만들 수 있음
- 데이터베이스와 별도 메시징 시스템을 함께 쓰면 race condition 가능성이 생김
- 예를 들어 메시지 하나는 디스크로, 하나는 네트워크로 가는 상황에서 관찰자가 둘을 다른 순서로 볼 수 있음
- Griffin은 단일 경로로 단순화하기 위해 FoundationDB에 기록하는 방식을 사용함
모노레포와 비즈니스 로직 격리
- Griffin은 monorepo를 사용함
- 현재는 효율을 위해 많은 log process가 같은 JVM에서 실행됨
- 각각은 독립적이라 별도 JVM process로 실행할 수도 있음
- 현재는 low hundreds 규모의 proc를 단일 JVM에서 실행함
- 비즈니스 로직은 최대한 단순하고 깨끗하게 유지함
- 개별 log processor namespace는 거의 전부 pure Clojure임
- 서드파티 라이브러리가 거의 없음
- side effect도 매우 적음
- log processor는 Clojure map을 입력으로 받아 하나 이상의 Clojure map을 반환하는 함수에 가까움
- proc state에는 protocol이 있어 반대편 구현을 몰라도 됨
- 테스트 시 in-memory database로 쓸 수 있음
- 실제 실행 시 FoundationDB에 쓸 수 있음
- 외부 세계와의 인터페이스는 가능한 작게 유지함
- 대부분의 proc는 자신의 내부 상태에 쓰고 메시지를 emit하는 것만 가능함
- 네트워크 호출 불가
- AWS 호출 불가
- 그 외 외부 동작 없음
- 외부 시스템과 통신해야 할 때는 전용 dispatch handler가 있는 특수 proc를 둠
- AWS와 통신하는 proc
- clearing bank와 통신하는 proc
- 다른 API와 통신하는 proc
사용하는 클로저 생태계
- 비즈니스 로직 내부에서는 라이브러리를 거의 쓰지 않음
- API web server나 service gateway처럼 외부 세계와 맞닿는 영역에서는 다음을 사용함
ringnettyreitit
- Clojure spec을 광범위하게 사용함
- AWS 연동에는 Cognitect
aws-api라이브러리를 사용함 - 애플리케이션 구성과 리소스 관리는 closeable 블로그 글에 기반한 방식을 사용함
- Component나 Integrant 없이
with-open으로 충분하다는 접근임 - lexical scope를 얻고, binding 선언 순서가 구성 순서를 강제함
Closeable을 구현하지 않는 상태 객체나 stateless 객체를with-open블록에서 선언하기 위한 작은 helper를 사용함
- Component나 Integrant 없이
채용과 팀 구성
- Griffin은 클로저 채용이 후보 수는 적지만 좋은 후보 비율이 높다고 봄
- Java 채용은 CV가 1,000개 와도 좋은 후보가 10명일 수 있음
- Clojure 채용은 CV가 13개 와도 좋은 후보가 10명일 수 있다고 표현함
- 작은 채용 풀에서는 원격 근무가 중요함
- 지역 제약을 줄이면 전 세계, 3개 시간대 내, 유럽 등 더 큰 풀을 만들 수 있음
- 짧은 기간에 엔지니어 100명을 뽑아야 하는 상황은 anti-pattern으로 봄
- 회사 전체 인원은 약 70명임
- Engineering은 약 22~24명임
- 약 3분의 2는 UK
- 약 3분의 1은 EU
- 독일 4명, 스웨덴 4명, 아일랜드 1명 수준
- 본사는 런던이지만 대부분의 개발자는 런던 밖 UK에 있음
은행 수준의 운영 복원성 테스트
- Griffin은 은행으로서 operationally resilient해야 하며, 이는 다운타임이 없어야 한다는 요구에 가깝다고 봄
- 돈을 다루기 때문에 문제가 생겨도 고객 돈을 잃지 않는다는 점을 실제로 증명해야 함
- 관심 있는 테스트 방향은 FoundationDB 팀의 방식과 유사함
- FoundationDB 팀은 데이터베이스 시뮬레이터를 구축함
- 약 20개의 process type, 즉 cluster 내부 역할을 single-threaded C++ 앱으로 작성함
- C++ 위에 actor-model concurrency compiler를 구축함
- 모든 system call과 network call을 protocol을 통해 수행해 오류를 주입할 수 있게 함
- multi-threading도 message sending 기반 actor model을 통해 처리함
- 이 환경에서는 결정적으로 오류를 주입할 수 있음
- 메시지 A와 B를 보냈지만 반대편에 B, A 순서로 도착하는 경우
- 메시지 처리 중 디스크 쓰기 오류가 나는 경우
- 이는
test.check의 generative testing과 비슷하게, 시스템의 모든 비결정성을 제어 가능한 단일 random number에서 seed하는 방식으로 볼 수 있음 - 제어하고 싶은 대상은 disk errors, network errors, message reordering임
- 현재 문제는 Java threading libraries, NIO, disk write의 동작을 제어할 방법이 없다는 점임
- Jepsen과 같은 정신을 공유하지만 차이가 있음
- Jepsen은 여러 VM을 두고 프로세스를 죽이는 식의 brute force에 가깝다고 봄
- 데이터베이스 상태를 내부에서 검사하기 어려워 커버리지를 알기 힘듦
- 완전히 제어 가능한 환경에서는 system call이나 메시지 interleaving을 열거하고, in-memory라 매우 빠르게 확인할 수 있음
- FoundationDB 팀은 이런 테스트 환경을 초기에 만들었고, 이는 FoundationDB에 대한 신뢰를 주는 요소로 작용함
- Griffin은 채용 중이며 Griffin careers page에서 정보를 확인할 수 있음