Ngô Minh Phúc

CodLuck Technology JSC

Kinh nghiệm tại CodLuck

Mình tách phần này thành 2 mục dự án. Mục 01 là SEC, một backend Spring Boot lớn và nhiều nghiệp vụ. Mục 02 là 3D viewer, một demo Web 3D giúp thay đổi texture/vật liệu của đồ vật trong không gian 3D.

Dự án đã tham gia / tìm hiểu

01

SEC - Education Management System

Hệ thống quản lý giáo dục với học viên, lớp học, giáo viên, thanh toán, điểm danh, ticket, notification, Zoom/Zalo và report Excel.

Backend / System
02

3D viewer

Ứng dụng Web 3D cho phép xem không gian kiến trúc và thay đổi texture/vật liệu của các bề mặt như sàn, tường, tủ hoặc nội thất theo thời gian thực.

Web 3D / Product

SEC - Những gì mình thật sự học được

SEC làm mình thấy rõ cảm giác đọc một backend doanh nghiệp thật: không phải lúc nào code cũng gọn như tutorial, cũng không phải mọi thứ đều có mapper, clean architecture hay annotation có sẵn. Có những nơi rất thực dụng, có những nơi hơi nặng, nhưng chính vì vậy mình học được nhiều hơn về trade-off trong dự án thật.

Vai trò

Tìm hiểu codebase, phân tích flow nghiệp vụ, đọc service/repository/scheduler/security và ghi lại những điểm đáng học trong backend.

Phạm vi

JWT security, phân quyền custom, scheduled jobs, notification đa kênh, OTP, audit log, JPA/Hibernate, native query/report, Excel import/export và tích hợp Zoom/Zalo.

  • Java 17
  • Spring Boot
  • Spring Security
  • JWT
  • Spring Data JPA
  • Hibernate
  • MySQL
  • Spring Scheduler
  • Spring AOP
  • Apache POI
  • Zoom API
  • Zalo / Firebase / Email
01

Codebase lớn hơn CRUD rất nhiều

SEC không thể đọc theo kiểu mở controller rồi lần từng API đơn lẻ. Nhiều flow chạy xuyên qua học viên, lớp học, lịch học, thanh toán, điểm danh, notification và Zoom meeting. Một hành động nhỏ ở UI có thể kéo theo cập nhật DB, tạo task nền, gửi thông báo và đồng bộ sang hệ thống ngoài.

02

DTO đi thẳng, không có mapper riêng

Dự án không tách mapper rõ ràng kiểu MapStruct hay mapper class riêng cho mọi entity. Nhiều chỗ không map entity sang DTO ở service, mà repository query trả thẳng DTO/projection để service lấy dùng luôn. Cách này giảm lớp trung gian và nhanh cho màn hình report/list, nhưng DTO bị gắn khá chặt với query.

03

Repository query trả DTO luôn

Phần report và màn hình quản trị khiến repository có nhiều JPQL/native query khá dài. Có query join nhiều bảng rồi trả thẳng về DTO dùng luôn, thay vì lấy entity ra rồi mapper sang response ở service. Cách này thực dụng cho list/search/export Excel vì lấy đúng field cần hiển thị, nhưng repository dễ thành một lớp query lớn, khó type-safe và dễ vỡ nếu DTO constructor/projection không khớp field.

04

Phân quyền bằng @AuthFilter + AOP

Phần phân quyền của SEC không dùng kiểu rải @PreAuthorize có sẵn, mà custom annotation @AuthFilter và bắt bằng AuthFilterAspect. Aspect dùng @Around để chặn method có @AuthFilter, lấy current user qua util.getCurrentUser(), gọi permissionService.getListByRoleId(user.getRoleId()), rồi so permission trong DB bằng modulePath + actionPath với danh sách permission khai báo trong annotation. Nếu khớp thì joinPoint.proceed(), nếu không thì throw ResponseStatusException(HttpStatus.FORBIDDEN).

05

Scheduler là phần làm dự án có cảm giác sống

Các scheduled job là phần mình thấy thú vị nhất. Dự án có job tạo/update/xóa Zoom meeting, lấy recording, tạo attendance, cập nhật trạng thái lớp, nhắc thanh toán, nhắc học, xử lý ticket quá hạn và upsale/payment schedule. Pattern mình học được là API lưu trạng thái/task vào DB, job nền quét và xử lý sau.

06

Notification được tách thành hệ thống riêng

SEC không để mỗi service tự gửi Zalo/Firebase/Email theo cách riêng. Backend tạo NotificationChannelMessage gồm templateCode, channelType và channelData rồi gọi notification API riêng. Cách này giúp core backend không hard-code nội dung tin nhắn, đồng thời gom tracking/template/channel về một nơi.

07

AOP audit log và JPA auditing giúp giảm lặp code

Dự án có BaseEntity/JPA auditing để tự động ghi createdAt, updatedAt, createdUser, updatedUser. Ngoài ra còn có custom annotation và aspect cho activity log. Lúc đầu đọc AOP hơi khó theo flow vì logic không nằm trực tiếp trong method, nhưng sau đó mình hiểu đây là cách tách cross-cutting concern như audit/logging ra khỏi business code.

08

Excel import/export là phần rất đời

Trước khi đọc SEC, mình hay nghĩ backend chủ yếu là API JSON. Nhưng dự án có nhiều template Excel, export report và import dữ liệu bằng Apache POI. Đây là phần code dễ dài, dễ sai index/style nhưng lại rất quan trọng với hệ thống nội bộ.

Điều mình rút ra sau khi đọc SEC

  • Dự án thật thường có nhiều quyết định thực dụng, không phải lúc nào cũng clean theo sách.
  • Không có mapper riêng giúp code nhanh hơn lúc đầu, nhưng DTO/service/repository dễ bị dính chặt với nhau.
  • Query report cần đọc kỹ vì bug thường nằm ở join, filter, projection và performance.
  • Phân quyền custom bằng AOP giúp bám nghiệp vụ hơn, nhưng cần hiểu rõ flow chạy ngầm.
  • Scheduler/polling worker là pattern quan trọng khi hệ thống phải xử lý external API hoặc tác vụ nền.

Mình cố tình không viết SEC như một danh sách công nghệ đẹp mắt. Phần giá trị nhất với mình là trải nghiệm đọc một codebase có cả điểm hay, điểm nặng, và nhiều quyết định rất thực tế.

3D viewer - Thay đổi texture trong không gian 3D

3D viewer là một demo Web 3D có mục tiêu giúp người dùng xem không gian kiến trúc trên trình duyệt và thay đổi texture/vật liệu của đồ vật trong không gian 3D. Thay vì nhìn bản vẽ 2D hoặc ảnh render tĩnh rồi tự tưởng tượng, người dùng có thể chọn vật liệu cho sàn, tường, tủ, nội thất và xem kết quả gần như ngay lập tức.

Mục tiêu

Tạo một viewer 3D tương tác để thay đổi texture/vật liệu của các bề mặt trong mô hình, giúp khách hàng hình dung không gian sau khi chọn gạch, gỗ, màu sơn hoặc vật liệu nội thất.

Phạm vi

Load model GLB, xử lý camera, đổi material/texture theo nhóm object, thử nghiệm tương tác chọn mesh, và dùng Supabase để quản lý nhóm vật thể/texture theo hướng động.

  • ReactJS
  • Three.js
  • WebGL
  • GLB Model
  • PBR Materials
  • Raycaster
  • camera-controls
  • Supabase
01

Bài toán là giảm khoảng cách tưởng tượng

Trong nội thất/xây dựng, khách hàng thường phải nhìn bản vẽ 2D, ảnh render hoặc catalogue vật liệu rồi tự tưởng tượng khi ghép vào nhà thật sẽ ra sao. 3D viewer giải quyết đúng khoảng trống đó: đưa không gian lên web, cho người dùng xoay nhìn hoặc đi vào bên trong, rồi đổi texture để thấy cảm giác vật liệu trong bối cảnh thật hơn.

02

Texture không chỉ là đổi ảnh

Khi đổi vật liệu trong Three.js, mình không chỉ thay một tấm ảnh lên mesh. Cần hiểu material, map texture, độ lặp texture, ánh sáng, độ bóng/nhám và cảm giác PBR để bề mặt như gạch, vân gỗ hoặc tường nhìn không bị giả. Đây là phần làm mình thấy Web 3D khác hẳn UI web thông thường.

03

Camera quyết định trải nghiệm

Dự án có 2 kiểu trải nghiệm camera: orbit mode để nhìn tổng thể mô hình và interior/FPS mode để đi vào bên trong không gian. Phần này không chỉ là kỹ thuật điều khiển camera, mà còn là cách giúp người xem cảm nhận tỷ lệ, chiều sâu và bố cục căn phòng.

04

Click-to-select từng là tính năng mình rất tự hào

Ban đầu mình làm tính năng click trực tiếp vào vật thể trong 3D bằng Raycaster: hover vào mesh thì highlight, click vào sofa/tường/sàn thì hiện menu để đổi vật liệu cho đúng vật thể đó. Về mặt kỹ thuật nó rất thú vị, gần giống cơ chế interaction trong game, và mình từng nghĩ đây sẽ là điểm demo ấn tượng nhất.

05

Mentor kéo mình về product mindset

Sau khi demo nội bộ, mentor góp ý rằng tính năng click chọn mesh tuy kỹ thuật rất hay nhưng có thể quá rối với khách hàng cuối. Người dùng không nhất thiết là người rành công nghệ; bắt họ rê chuột trong không gian 3D để tìm đúng mesh đang highlight có thể tạo friction. Thay vào đó, UI nên có danh sách rõ ràng như Sàn nhà, Tường, Tủ, Bàn ghế; người dùng chỉ cần chọn nhóm rồi chọn texture.

06

Đơn giản hơn nhưng tốt hơn

Việc bỏ hoặc vô hiệu hóa phần code phức tạp mình đã mất nhiều thời gian làm không dễ chịu, nhưng sau khi chuyển sang UI menu cố định, ứng dụng trở nên rõ ràng hơn hẳn. Người mới nhìn vào biết ngay phải bấm đâu để đổi màu sàn hoặc đổi vật liệu tường, không cần giải thích thao tác chuột trong không gian 3D.

07

Supabase giúp cấu hình động

Một hướng quan trọng của dự án là không fix cứng mọi thứ trong code. Supabase được dùng để admin có thể cấu hình nhóm object, ví dụ nhóm tường, nhóm sàn, nhóm tủ, rồi map từng nhóm với thư viện texture tương ứng. Nhờ vậy viewer có thể mở rộng vật liệu hoặc nhóm đối tượng mà không phải sửa logic render quá nhiều.

08

Bài học lớn nhất không phải WebGL

Dự án giúp mình học Three.js/WebGL, GLB, camera và material, nhưng bài học lớn nhất lại là tư duy sản phẩm: khách hàng không mua độ phức tạp của code, họ mua một cách dễ hơn để ra quyết định. Một tính năng càng kỹ thuật không có nghĩa là càng tốt nếu nó làm người dùng mất nhiều công để hiểu.

Điều mình rút ra từ 3D viewer

  • Mục tiêu chính của viewer là giúp người dùng hình dung texture/vật liệu trong không gian 3D, không phải phô diễn interaction phức tạp.
  • Three.js/WebGL buộc mình quan tâm đến camera, material, ánh sáng, texture repeat và hiệu năng model.
  • Raycaster và mesh highlighting rất thú vị về kỹ thuật, nhưng không phải lúc nào cũng là UX tốt nhất.
  • UI menu cố định có thể kém ngầu hơn click trực tiếp vào model, nhưng dễ hiểu và phù hợp với khách hàng hơn.
  • Một product engineer cần biết khi nào nên bỏ tính năng mình thích để giữ trải nghiệm người dùng đơn giản.

Với 3D viewer, mình học được rằng sản phẩm tốt không phải là sản phẩm có nhiều thứ phức tạp nhất, mà là sản phẩm giúp người dùng đạt mục tiêu nhanh và ít bối rối nhất.