Đồ án năm ba · Dublin City University · 2023—2024
Bynle
Nền tảng bán vé cho các câu lạc bộ và hội sinh viên — sự kiện, thanh toán, soát vé bằng mã QR và một lớp mạng xã hội — được dựng để ban điều hành bán vé trực tiếp, còn sinh viên thì có thể trao lại vé cho bạn một cách an toàn.
- Django
- React
- PostgreSQL
- Stripe Connect
- QR ticketing
- AWS
- Thời gian
- 2023 — 2024
- Bối cảnh
- Đồ án năm ba, Cử nhân Khoa học Máy tính
- Trường
- Dublin City University
- Triển khai
- Railway, AWS Amplify và S3
- 134
- Kiểm thử đơn vị
- 10
- Thực thể nghiệp vụ
- 3
- Nơi triển khai
- 2
- Loại tài khoản
Bao phủ các model và view của backend, không thay đổi nào được gộp mà chưa qua rà soát.
Người dùng, hồ sơ, câu lạc bộ, sự kiện, vé, tài khoản Stripe, lượt theo dõi, quan hệ bạn bè và yêu cầu chuyển nhượng.
Django và PostgreSQL trên Railway, React trên AWS Amplify, tệp tải lên nằm trong bucket S3.
Người dùng thường và tài khoản soát vé, tách bạch trong token lẫn trong cây định tuyến.
01Ý tưởng
Từ chuyện bán lại vé đến chuyện lo trọn cả sự kiện
Dự án bắt đầu từ một chỗ khác. Ý tưởng ban đầu là một lớp chuyển nhượng an toàn đặt lên trên các nền tảng bán vé sẵn có: một người khởi tạo việc trao vé, người kia trả tiền ngay trong ứng dụng, và vé chỉ đổi chủ khi khoản thanh toán đã thành công — một cách chữa cho những vụ lừa đảo bán lại vé đi kèm mọi sự kiện cháy vé.
Chúng tôi từ bỏ hướng đó. Khi làm việc với API của các nền tảng ấy, chúng tôi không hài lòng với mức bảo mật họ cung cấp lẫn mức độ xác minh mà mình thực sự làm được. Nếu việc xác minh phải đáng tin, thì tấm vé phải là của chúng tôi.
Thế là chúng tôi dựng luôn hệ thống bán vé, nhắm vào nơi mình hiểu rõ nhất: các câu lạc bộ và hội sinh viên. Câu lạc bộ tạo sự kiện, bán vé trực tiếp và quét vé ngay tại cửa. Và vì giờ đây tấm vé thuộc về chúng tôi từ đầu đến cuối, việc chuyển nhượng an toàn — thứ khởi nguồn cả dự án — trở thành điều chúng tôi thực sự bảo đảm được.
Bước chuyển cuối cùng là mạng xã hội. Một khi sinh viên đã tìm thấy sự kiện ngay trên nền tảng, việc khám phá chỉ là một danh sách thì chẳng còn mấy ý nghĩa. Theo dõi câu lạc bộ, kết nối bạn bè và thấy được mọi người sắp đi đâu đã biến một công cụ bán vé thành thứ gần với bảng tin của cả khuôn viên trường.
02Sản phẩm
Ba kiểu người dùng, một nền tảng
Bynle có ba kiểu người dùng, và giao diện đổi hình hài theo từng kiểu.
- Sinh viên
- Xem sự kiện, theo dõi câu lạc bộ, gửi và nhận lời mời kết bạn, mua vé, và trao lại cho bạn tấm vé mình không dùng đến.
- Quản trị câu lạc bộ
- Tạo và quản lý sự kiện, đặt giá vé, kết nối tài khoản Stripe, sửa trang câu lạc bộ, bổ nhiệm quản trị viên khác, tạo tài khoản soát vé và xem thống kê của câu lạc bộ.
- Người soát vé
- Một loại tài khoản riêng do quản trị viên câu lạc bộ tạo ra, với đúng một nhiệm vụ: đăng nhập trên điện thoại ở cửa và quét mã QR.
- Sự kiện. Câu lạc bộ đăng sự kiện kèm ngày, giờ, địa điểm, sức chứa, thể loại và ảnh bìa, miễn phí hoặc có thu phí.
- Vé. Mỗi vé mang một mã riêng và một mã QR được sinh ra, gắn với đúng một sự kiện và một chủ sở hữu.
- Chuyển nhượng. Vé có thể trao lại cho bạn bè, với phần thanh toán xử lý ngay trong ứng dụng khi vé không miễn phí.
- Đồ thị quan hệ. Lời mời kết bạn có trạng thái chờ, lượt theo dõi câu lạc bộ, và những mối quan hệ chung giữa hai người dùng.
- Thống kê câu lạc bộ. Quản trị viên xem được những ngành học và khoá nào đang theo dõi câu lạc bộ, tổng hợp ở backend và hiển thị thành biểu đồ.
- Tệp phương tiện. Logo, ảnh bìa câu lạc bộ và áp phích sự kiện do quản trị viên tải lên, phục vụ từ kho lưu trữ đám mây chứ không phải từ máy chủ ứng dụng.


03Kiến trúc
Năm lớp, ba nơi lưu trú
Hệ thống tách thành một client React, một lớp ứng dụng Django, một lớp dữ liệu PostgreSQL, một lớp tích hợp cho thanh toán và tệp phương tiện, và một lớp triển khai đặt từng mảnh vào đúng chỗ của nó.
Backend là một API REST. Các view của Django được gom thành những bộ xử lý theo từng miền — xác thực, câu lạc bộ, thống kê, sự kiện, quan hệ bạn bè, soát vé, thanh toán, vé, chuyển nhượng và dữ liệu người dùng — với serializer kiểm tra mọi thứ đi vào và chuyển các bản ghi model thành JSON khi đi ra.
Toàn bộ chạy trên ba dịch vụ. Ứng dụng Django và một container PostgreSQL nằm cùng nhau trên Railway, nơi tự lấy phiên bản mới nhất của một nhánh cố định sau mỗi lần đẩy mã và chạy kịch bản khởi động để migrate, gom tệp tĩnh rồi bật Gunicorn. Frontend React đặt trên AWS Amplify. Tệp tải lên — logo, ảnh bìa và áp phích — nằm trong bucket S3 chứ không nằm trên máy chủ ứng dụng.
- Client
- React, với React Router canh giữ các tuyến riêng tư và một cây tuyến tách biệt dành cho tài khoản soát vé.
- Ứng dụng
- Các view REST của Django gom thành mười bộ xử lý theo miền, kèm serializer để kiểm tra dữ liệu và chuyển đổi JSON.
- Dữ liệu
- PostgreSQL cho dữ liệu có cấu trúc, chạy từ một image Docker; một bucket S3 cho tệp tải lên.
- Tích hợp
- Stripe cho thanh toán và đăng ký Connect, kèm một bước kiểm tra trạng thái quyết định câu lạc bộ hay người dùng có được phép thu tiền hay không.
- Triển khai
- Railway cho backend và cơ sở dữ liệu, AWS Amplify cho frontend, và quản lý phiên bản bằng Git cho cả hai.


04Vé
Bán một tấm vé thì dễ; dịch chuyển nó an toàn thì không
Một tấm vé chỉ có giá trị khi đúng một người dùng được nó, đúng một lần. Ràng buộc đó định hình ba phần khó nhất của hệ thống: tiền đến tay câu lạc bộ ra sao, vé đổi chủ thế nào, và chuyện gì xảy ra ở cửa.
Phần chuyển nhượng là chỗ chúng tôi làm sai trước tiên. Ban đầu, tấm vé đang trong quá trình trao đi vẫn là một tấm vé hợp lệ — nghĩa là nó có thể bị quét ngay khi giao dịch còn dang dở, và ai đó có thể vào sự kiện bằng chính tấm vé mình đang cho đi. Cách sửa là cho vé một trạng thái chuyển nhượng của riêng nó.
- Stripe Connect. Câu lạc bộ thu tiền qua tài khoản Stripe Connect của chính họ chứ không qua chúng tôi. Hệ thống kiểm tra tài khoản đó đã hoàn tất chưa và không cho phép thu phí sự kiện chừng nào chưa xong.
- Trạng thái chuyển nhượng. Vé đang được trao đi bị giữ ở trạng thái chuyển nhượng. Nó quét ra không hợp lệ và không thuộc về bên nào cho tới khi người nhận chấp nhận hoặc người gửi huỷ.
- Đổi chủ sở hữu. Khi được chấp nhận, vé cũ bị xoá và một vé mới được tạo cho người nhận, nên vé đã chuyển nhượng là một bản ghi mới chứ không phải bản ghi bị sửa.
- Chuyển nhượng có thu phí. Với vé từng có giá, người nhận trả tiền ngay trong ứng dụng và giao dịch chỉ hoàn tất sau khi khoản thanh toán được xử lý.
- Mã QR ở cửa. Mỗi vé mang một mã QR trỏ tới điểm truy cập xác thực mà chỉ tài khoản thuộc loại soát vé mới được phép chạm tới.
- Tài khoản soát vé. Quản trị viên tạo tài khoản soát vé cho từng sự kiện. Chúng có thông tin đăng nhập riêng, cây tuyến riêng, và không làm được gì ngoài quét vé.


05Chất lượng
Chúng tôi đã kiểm thử gì, và cái gì đã hỏng
Việc kiểm thử chạy trên ba hướng: rà soát mã ở mọi merge request, kiểm thử đơn vị cho backend, và các buổi thử nghiệm với sinh viên thật.
Backend khép lại với 134 kiểm thử đơn vị trải trên model và view — tính toàn vẹn dữ liệu cùng quy tắc kiểm tra ở một bên, xử lý yêu cầu và logic nghiệp vụ ở bên kia, gồm cả những phép kiểm tra quyền như xác nhận rằng chỉ tài khoản soát vé mới chạm được tới điểm xác thực vé. Không thay đổi nào vào được nhánh chính mà người kia chưa rà soát.
Thử nghiệm với người dùng đã thay đổi sản phẩm. Hai đề nghị lặp lại đủ rõ để chúng tôi bắt tay vào làm:
- Chuông thông báo. Người dùng muốn với tới lời mời kết bạn và các lượt chuyển vé đến từ bất cứ đâu, thay vì phải lần vào đúng trang chứa chúng. Một cái chuông kèm số đếm được đưa lên thanh điều hướng.
- Xoá lượt chuyển đã gửi. Người tham gia muốn rút lại lượt chuyển vé đã gửi nhầm, hoặc sau khi đổi ý. Các lượt chuyển đã gửi trở nên xoá được.

Cái gì đã hỏng
01
Quản trị viên câu lạc bộ bị mô hình hoá thành một lớp riêng
Vấn đề
Ban đầu quản trị viên câu lạc bộ là một lớp model riêng. Đó là hình dạng sai: quan hệ giữa người dùng và câu lạc bộ vốn là nhiều-nhiều, và ép nó đi qua một lớp riêng khiến việc quản trị trở nên vụng về.
Cách khắc phục
Tái cấu trúc sang quan hệ nhiều-nhiều, cho phép nhiều người quản trị nhiều câu lạc bộ mà không rườm rà, và làm gọn toàn bộ đường đi của việc quản trị.
02
Hai người, những migration bị xoá, một cơ sở dữ liệu vỡ
Vấn đề
Giai đoạn đầu, hai đứa làm trên hai nhánh riêng và cùng sinh migration mỗi khi đổi model. Cả hai đều chưa hiểu migration của Django đủ sâu, và cả hai đều từng xoá bớt. Khi rebase để gộp vào main, cơ sở dữ liệu vỡ: nó không còn khớp với model nữa, và chúng tôi mất rất lâu mới tìm ra vì sao.
Cách khắc phục
Chúng tôi thống nhất không bao giờ xoá migration nữa, trừ khi cơ sở dữ liệu đã sao lưu và việc xoá sạch là thực sự cần thiết. Một kịch bản dựng lại dữ liệu từ đầu giúp chúng tôi khôi phục nhanh một bộ dữ liệu chạy được trong lúc gỡ rối.
03
Vé vẫn hợp lệ khi đang chuyển nhượng
Vấn đề
Trong lúc một lượt chuyển vé còn dang dở, tấm vé vẫn ở trạng thái hoạt động, tạo nguy cơ có người vào cửa trái phép bằng chính tấm vé đang được trao đi.
Cách khắc phục
Vé giữ trạng thái chuyển nhượng cho tới khi người nhận chấp nhận hoặc người gửi huỷ. Ở trạng thái đó, vé không quét được và bị tính là không hợp lệ tại cửa.
04
Triển khai ba mảnh cùng lúc
Vấn đề
Đưa backend Django, frontend React và cơ sở dữ liệu lên cùng nhau ngốn nhiều ngày đọc tài liệu và một chuỗi dài những lần triển khai thất bại ở phía máy chủ backend.
Cách khắc phục
Railway, vốn tích hợp với kho mã và tự lấy phiên bản mới nhất của một nhánh cố định, dựng được backend cùng cơ sở dữ liệu trong một thực thể dùng chung; còn frontend thì chuyển sang AWS Amplify.
Công nghệ
Xây dựng bằng
Frontend
- React
- React Router
- JavaScript
- Axios
Backend
- Django
- Python
- Serializer
- JWT
- Gunicorn
Dữ liệu
- PostgreSQL
- Docker
- AWS S3
Nền tảng
- Railway
- AWS Amplify
- Stripe Connect
- Pexels API
- GitLab CI
Ghi nhận
Nhóm thực hiện và tài liệu
Thực hiện như đồ án năm ba của nhóm hai người cho chương trình Cử nhân Khoa học Máy tính tại Dublin City University — cùng cặp đôi đã làm Tường lửa SMS cho 5G một năm sau đó.
- Cùng thực hiện
- Jack Keenan
- Trường
- Dublin City University, 2023—2024
- Thanh toán
- Stripe Connect
- Tài liệu
- Tài liệu kỹ thuật và hướng dẫn sử dụng, công bố kèm mã nguồn.