Case study, super app

Xác thực bổ sung mà luật chơi nằm trong config

Mình phụ trách challenge-chain service, chỗ quyết định khách phải chứng minh những gì trước khi làm một việc nhạy cảm. Đây là cách nó được ráp lại, và nó giúp team nhẹ gánh ở đâu.

Bài toán

Trong super app, có việc chỉ đăng nhập thôi là chưa đủ: chuyển tiền, đăng nhập trên máy mới, đặt PIN. Mỗi việc cần một bộ kiểm tra riêng, và luật thì đổi liên tục. Nếu luật nằm trong code dưới dạng if-else, đổi một luật là sửa code rồi release.

Nó nằm ở đâu

BFF biết vì sao cần kiểm tra nên nó mở chain cho hành động đó. App trả lời từng challenge qua gateway. Xong chain, service thực hiện hành động hỏi lại challenge-chain service xem phiên đã đạt yêu cầu chưa, đạt rồi mới làm.

Challenge-chain service nằm ở đâu BFF mở chain, app trả lời challenge qua API gateway, service kiểm tra câu trả lời bằng mỗi loại challenge một adapter, giữ phiên trong Redis, config và lịch sử trong PostgreSQL, còn service thực hiện hành động kiểm tra phiên trước khi làm. App mobile / web API gateway BFF Service thực hiệnhành động Challenge chain engine · planner validator assurance rules Adapter Credential OTP Selfie ID card NFC PIN, T&C Redisphiên PostgreSQLconfig · lịch sử 1 2 3 mở trả lời kiểm tra
1. BFF mở chain cho một hành động. 2. App trả lời từng challenge. 3. Service thực hiện hành động kiểm tra kết quả rồi mới làm.

Chain là dữ liệu

Mỗi hành động gắn với một chain. Chain nói khách phải chứng minh điều gì, còn engine tự lên kế hoạch các bước để đáp ứng. Chain và action lưu dạng JSONB, được đưa qua các môi trường bằng Liquibase. Version chỉ được thêm, không bao giờ sửa, nên lần thử nào cũng đọc lại được theo đúng version nó đã chạy.

Ví dụ, đăng nhập trên máy mới có thể đòi PIN, selfie, ảnh giấy tờ và quét chip NFC. Chuyển tiền vượt ngưỡng thì đòi thêm selfie ngoài OTP.

Mức đảm bảo và mức sàn

Mỗi bước chứng minh được tính vào một loại bằng chứng, cộng lại thành mức đảm bảo. Mỗi hạng khách có một mức sàn trong config mà hành động phải đạt. Đặt PIN hay đồng ý điều khoản thì không cộng điểm nào, vì đăng ký credential chẳng nói lên được ai đang cầm điện thoại.

ChallengeTính là bằng chứng gì
Mật khẩu hoặc PINThứ mình biết
OTPThứ mình có
SelfieSinh trắc học
Ảnh giấy tờGiấy tờ, ảnh chụp
Quét chip NFCGiấy tờ, chip

Luật trước khi version lên sóng

Version chain mới nào cũng phải qua validator mới được dùng. Vài luật tiêu biểu:

Mọi config đều đi qua đúng một validator này, nên nguồn config thêm vào sau cũng không lách được.

Bên trong service

Service có ba module. core chứa engine, planner, validator và luật đảm bảo, ArchUnit đánh fail build nếu nó dám import Spring Web, JPA, Redis, Kafka hay SDK của vendor. api là module duy nhất biết HTTP. infra giữ mỗi loại challenge một adapter, cộng với các store. Danh mục challenge được dựng từ các adapter đã đăng ký lúc khởi động, hai adapter giành cùng một loại là service không chịu boot.

Phiên nằm trong Redis có TTL. Config và lịch sử lần thử nằm trong PostgreSQL. Lúc bắt đầu một lần thử được ghi đồng bộ trước khi OTP bị dùng, nên Redis có mất thì dấu vết khách đã dùng gì vẫn còn.

Các module của service Module api và infra đều phụ thuộc vào core. Core không dính framework hay vendor nào. Infra hiện thực các port bằng adapter, store phiên trong Redis, store config trong PostgreSQL và Caffeine cache. api controller DTO · error envelope module duy nhất biết HTTP core engine planner · validator luật đảm bảo port không Spring Web, JPA, Redis, Kafka hay SDK infra mỗi loại challenge một adapter phiên trong Redis PostgreSQL · Caffeine gọi hiện thực
Mọi phụ thuộc đều chĩa vào core, và ArchUnit canh giữ chuyện đó.

Giữ đường nóng nhẹ nhàng

Profiling cho thấy mỗi lần start chain tốn ba query, mỗi lần switch tốn năm. Mình đặt Caffeine cache trước phần đọc config trên đường nóng, dữ liệu giữa các pod chỉ cũ tối đa bằng TTL, và cache được xoá tại chỗ khi có config mới thêm vào.

Được gì

Chi tiết đã được đơn giản hoá và không nêu tên khách hàng.