Note lại vài trang web hay hay, sau này lấy đó làm tư liệu. Đáng lẽ việc này phải làm từ lâu rồi, nhưng không hiểu sao đến giờ mới bắt tay thực hiện. Danh sách sẽ không có thứ tự ưu tiên, chỉ là nhớ ra thứ gì, hoặc tình cờ gặp lại thứ gì đó thì sẽ note lại.
Photo
Đầu tiên, là https://photos.tinhte.vn/ là nơi tinhte chia sẻ photos.
Nói về photo chút, chụp ảnh với nhiều người nó là một thú chơi, với nhiều người khác đó là sở thích và đam mê. Mê tới mức nghe tiếng xoạch xoạch khi bấm máy là đã “phê” rồi.
Lâu rồi mình cũng không chụp ảnh một-cách-tử-tế. Tức là chụp ảnh mà muốn bức ảnh kể một câu chuyện. Có một dạo chụp vì ham thích kĩ thuật và “đồ chơi” tức là máy (body) và ống kính (lens). Nhưng sau rồi cũng bỏ. Hồi đó biết nhiều kĩ thuật, nhưng ảnh ít có hồn. Có chụp cảnh thì dễ truyền tải câu chuyện hơn, nên chụp khá nhiều “cảnh”, nhất là Hà Nội đêm.
Update 01/2023: tinhte đã bỏ chuyên mục này hoặc dời sang nơi khác.
Ngoài khoa học ra, giáo sư là người viết nhiều về định hướng, tư duy, và có nhiều tư tưởng về việc truyền đạt tri thức. Recommend nên ghé thăm nếu bạn là người có growth mindset.
Một trong các đòi hỏi lớn của việc quản lý danh mục sản phẩm là phải thiết kế để đáp ứng các nhu cầu về hiệu năng và tích hợp với các hệ thống khác. Để đáp ứng các yêu cầu tốt, thì phải được thiết kế từ tổng thể ngay từ đầu. Có vậy mới tạo ra sự phát triển liền mạch, nhất quán giúp đảm bảo tính ổn định, cũng như tiến độ làm việc.
Mô hình CQRS — ES.
Mô hình Event Sourcing — ES, là mô hình thiết kế mà trạng thái của object sẽ được lưu trữ dưới dạng chuỗi các sự kiện thay đổi. Nó khác với mô hình thiết kế thông thường, khi mà chỉ lưu trữ trạng thái cuối cùng của object.
Khi object bị thay đổi, sẽ tương ứng với một versioned event lưu trữ dữ liệu thay đổi của object. Các event sẽ được lưu trữ dạng appending only vào một cấu trúc table gọi là event store. Việc lưu trữ các event thay đổi này giúp mang lại các lợi ích:
– Lưu trữ được lịch sử thay đổi của các đối tượng.
– Nếu các event được gửi tới các hệ thống khác, các hệ thống đó có thể sinh ra trạng thái cuối cùng chính xác của đốc tượng gốc. Do đó dễ dàng tích hợp với các hệ thống khác.
Dựa trên các event được published đi có thể xây dựng phần lưu trữ riêng cho read side, để tối ưu cho tốc độ truy xuất dữ liệu.
Event Bus — Kafka — MySql Binlog.
Các versioned event của đối tượng nếu được gửi đi sẽ giúp hệ thống dễ dàng tích hợp với nhiều hệ thống khác. Event Bus là tên gọi logic của một đường truyền chứa tất cả các versioned event dưới dạng stream. Các consumer chỉ việc subscribe event bus là có thể nhận được các event để xử lý.
Các event được lưu trữ trong mysql database. Tất cả các thay đổi trong database đều được MySql lưu vào một log file, gọi là binlog. Bằng việc extra binlog của mysql, thì hệ thống có thể bắt được mọi event phát sinh để gửi đi. Kafka là một message broker theo mô hình log based, cho phép lưu trữ và gửi đi các event đúng với thứ tự gửi vào. Vì vậy nó là lựa chọn tốt nhất để làm tầng lưu trữ cho event bus.
Kiến trúc tổng thể.
Đên đây bức tranh của hệ thống đã trở nên rõ ràng. Từng mảnh ghép từ model, nghiệp vụ tới tích hợp đã đầy đủ. Có thể tóm tắt lại:
– Hệ thống sẽ xây dựng nghiệp vụ quanh model product;
– Cấu trúc theo mô hình CQRS — ES;
– Sử dụng MySql Binlog, Kafka để publish các event thay đổi một cách ổn định;
– Dựa trên event bus sẽ xây dựng các cấu trúc lữu trữ có tốc độ truy xuất cao như Elastic, MongoDb, cũng như tích hợp với các hệ thống khác.
Kết luận
Thiết kế hệ thống quản lý danh mục sản phẩm trong ecommerce đòi hỏi phải đạt được cùng lúc nhiều mục tiêu, với một tầm nhìn xuyên suốt và nhất quán. Từ mức ứng dụng cho tới mức hệ thống đều phải có sự gắn kết liền mạch. Tất cả hướng tới hai mục tiêu quan trọng phải đạt được:
– Xử lý được các nghiệp vụ phức tạp
– Đảm bộ hiệu năng và độ ổn định của hệ thống.
Phương pháp phân tích thiết kế Domain Driven Desing, mô hình CQRS — ES, cơ chế replicate ổn định của MySql, sự đảm bảo thứ tự message của Kafka, cũng như các database tối ưu cho read như Elastic, Mongo… là các nhân tố chính đảm bảo chất lượng của hệ thống.
Đầu tiên chúng ta bắt đầu với bài toán quản lý sản phẩm với các nghiệp vụ phức tạp. Ví dụ hệ thống quản lý sản phẩm của các trang thương mại điện tử (Amazon, shoppe, tiki..).
Với bất kì sản phẩm phần mềm nào, chúng ta đều có chung mục đích thiết kế:
– DRY (Don’t Repeat Yourself), các nghiệp vụ được phân tách rõ ràng, cô đọng và tập trung cao.
– Có thể mở rộng, sửa đổi các thành phần độc lập.
– Linh hoạt trong việc phát triển.
– Dễ dàng test và bảo trì.
Các mục tiêu này sẽ ảnh hưởng tới cách thiết kế mô hình ứng dụng khác nhau. Chúng ta sẽ phân tích 3 mô hình thiết kế hay được sử dụng để tìm hiểu ưu, nhược điểm cũng như chọn cách thiết kế hệ thống phù hợp nhất.
Mô hình MVC
Mô hình MVC là mô hình phổ biến trong việc lựa chọn và xây dựng ứng dụng.
Đó là mô hình đơn giản, dễ tiếp cận và nhanh chóng đưa ra các tính năng.
Theo mô hình MVC thì phần quản lý sản phẩm sẽ theo mô hình như sau:
Với mô hình MVC như trên việc xử lý ở product model sẽ trở nên phức tạp. Lúc này product model sẽ là nơi:
– Định nghĩa các thuộc tính của product.
– Truy xuất database.
– Và thực thi các nghiệp vụ.
Thông thường mô hình MVC sẽ sử dụng pattern Active Record để mapping với database. Đây là cách làm đơn giản, nhưng có các hạn chế sau:
– Model product trở nên quá nặng khi chứa quá nhiều các logic từ định nghĩa thuộc tính, truy xuất dữ liệu, thực thi nghiệp vụ…
– Do không phân tách được các tầng nghiệp vụ và dữ liệu, nên khó thực hiện unit test các thành phần riêng biệt.
– Việc thay đổi logic truy xuất dữ liệu và logic nghiệp vụ khó khăn. Do các thành phần bị phụ thuộc vào nhau.
– Tính đóng gói của nghiệp vụ không đảm bảo. Các nghiệp vụ sẽ chỉ được cài đặt theo mô hình CRUD. Vì vậy càng về sau sự trùng lặp các logic càng cao.
Do các hạn chế đó nên đây không phải mô hình phù hợp với các ứng dụng logic phức tạp như ecommerce. Nó sẽ dẫn tới chi phí maintain về sau cao.
Mô hình ba lớp
Hướng tiếp cận tiếp theo là phân tích và thiết kế theo phương pháp n-tier hay Domain Driven Design. Đặc trưng của mô hình này là:
– Phân tách tầng nghiệp vụ khỏi tầng ứng dụng và tầng truy xuất cơ sở dữ liệu. Tầng này gọi là Domain Layer. Tầng này là nơi chứa tất cả các logic nghiệp vụ.
– Trong tầng nghiệp vụ, tập trung vào design model sao cho model phản ánh đầy đủ nhất tính chất nhất quán của nghiệp vụ. Tầng này có thể chia thành 2 phần riêng biệt là Domain Model và Domain Service. Domain Service đóng vai trò cung cấp các nghiệp vụ ra bên ngoài (tới tầng Aplication) xoay quanh các domain model của hệ thống.
– Thiết kế tầng infrastructure chứa các logic về truy xuất dữ liệu, thao tác với database, messeage queue.
DDD Partern
Lợi ích:
Với việc tổ chức hệ thống này, các tầng ứng dụng, nghiệp vụ và data access sẽ chia tách riêng biệt, và chỉ tương tác thông qua interface. Các nghiệp vụ sẽ xoay quanh model product. Tầng data access sẽ truy xuất hoặc lưu trữ các object products. Hạn chế:
Việc áp dụng mô hình 3 lớp xoay quanh model product cũng dẫn tới khó khăn là việc thiết kế model cho các nghiệp vụ đọc-dữ-liệu.
Đồng thời nó cũng ảnh hưởng tới tốc độ phát triển của hệ thống khi cài đặt các nghiệp vụ query đa dạng và phức tạp trong khi phuc vụ client. Việc này bị ảnh hưởng bởi cách thiết kế model và tầng Repository.
Mô hình CQRS
Mô hình CQRS (Command Query Responsibility Segregation) là sử mở rộng của mô hình trong ba lớp trong DDD. Đặc trưng quan trọng của CQRS là việc tách hai phần logic đọc và ghi dữ liệu ra hai phần riêng biệt:
– Phần ghi dữ liệu: được thực hiện qua việc send các command tới các handler thông qua command bus. Comand hanlder đóng vai trò tương tự domain service sẽ tương tác với các model để thực hiện các nghiệp vụ thay đổi dữ liệu.
– Phần đọc dữ liệu: được thiết kế riêng không lệ thuộc vào các model của phần ghi dữ liệu. Do đó có thể linh hoạt trong việc truy xuất database, cũng như sử dụng các data source khác nhau để tối ưu về tốc độ truy xuất.
Mô hình CQRS đã khắc phục các hạn chế đã nêu ở mục 2 bên trên. CQRS mang lại các lợi thế lớn:
– Cho phép phát triển và tối ưu phần đọc dữ liệu riêng biệt với phần ghi dữ liệu.
– Việc mô hình hoá các nghiệp vụ ghi dữ liệu dưới các command cho phép che đậy tốt các logic nghiệp vụ, giúp việc mở rộng dễ dàng hơn. Đồng thời các command đó có thể dễ dàng chuyển đổi giữa xử lý đồng bộ và bất đồng bộ thông qua lớp abstract là command bus mà không thay đổi mô hình. Giúp cung cấp một mô hình nhất quán, xuyên suốt trong bộ kiến trúc.
Tóm tắt về 3 mô hình (cả 3 mô hình trên đều có ưu/nhược điểm)
– Mô hình MVC đơn giản, nhưng hạn chế khi giải quyết nghiệp vụ phức tạp và testing, mở rộng
– Mô hình ba lớp phù hợp cho việc xử lý nghiệp vụ phức tạp, nhưng có nhiều hạn chế khi tối ưu phần đọc ra.
– Mô hình CQRS mở rộng từ mô hình ba lớp, giải quyết tốt việc chia tách đọc ghi, nhưng cũng đòi hỏi phải thiết kế phức tạp hơn.
Trên đây, chúng ta đã tìm hiểu qua về 3 mô hình khi thiết kế hệ thống. Và với các hệ thống lớn, cần đòi hỏi xử lý nghiệp vụ phức tạp, đồng thời đáp ứng hiệu năng, mô hình chúng ta nên chọn là CQRS. Mặc dù còn hạn chế về độ phức tạp khi triển khai, đòi hỏi team work cùng hiểu về hệ thống, tuy nhiên những giá trị mà nó mang lại nên được đánh đổi.
Phần tiếp theo chúng ta sẽ tìm hiểu tiếp về mô hình CQRS-ES.
Xuất phát từ mấy vấn đề nên muốn cover lại kiến thức về static và singleton.
– Một là để hiểu rõ thêm vấn đề
– Hai là làm rõ thêm vấn đề
Vậy static có đặc điểm gì? Khi nào nên dùng nó?
Singleton thì sao? Dùng thế nào? Các vấn đề cần lưu ý khi sử dụng?
Ok, xắn vào tìm hiểu static trước.
Định nghĩa: một class static sẽ không cho bạn tạo một cài đặt. Nghĩa là bạn sẽ không thể sử dụng từ khoá new để tạo object. Muốn sử dụng methods và properties cần khai báo thêm từ khoá static và gọi trực tiếp bằng tên lớp.
Đặc điểm
– Có vùng nhớ riêng, không bị thay đổi.
– Chỉ có một refrence duy nhất trong ứng dụng.
– Do không thể tạo instantiated, nên không cần cấp phát thêm vùng nhớ.
– Life cycle giống application.
– Khởi tạo một lần duy nhất khi chạy chương trình.
Nên dùng khi nào
– Xây dựng các lớp không phụ thuộc vào object. Ví dụ class Utility, lưu các thông tin dùng chung cho toàn bộ ứng dụng.
– Tiết kiệm bộ nhớ.
Ok, giờ tới singleton.
Mình xin được copy lại góc nhìn của Hiển blog guru Vì sao ra đời?
Trong GoF, Singleton được đưa ra với mục đích sau: “Ensure a class only has one instance, and provide a global point of access to it.” – “Đảm bảo một class chỉ có duy nhất một instance, và cung cấp một điểm truy cập duy nhất trên toàn cục tới instance.”
Lợi ích:
Nó cung cấp ý tưởng của việc một instance duy nhất, điều mà chúng ta gặp trong nhiều bài toán thực tế: một ứng dụng được khởi tạo, một cấu hình hệ thống, một logger…
Đặc điểm:
Về mặt runtime
– Tiết kiệm: Không giống như static trong class, object và các giá trị trong đó chỉ được khởi tạo khi cần thiết. Memory và cả CPU đều được tiết kiệm (static khởi tạo ngay khi chạy chương trình).
– Chủ động quản lý: Cho phép chủ động quản lý life cycle, giải phóng khi cần thiết.
Về mặt design
– Có khả năng thừa kế.
– Có thể kết hợp với các partern khác.
- Abstract hơn sử dụng static trong class.
Bàn thêm về singleton
Tư tưởng: giải quyết 2 bài toán khác nhau (dù có vẻ liên quan): “Ensure a class only has one instance, and provide a global point of access to it.”
– Một class có duy nhất một instance;
– Cung cấp một điểm truy cập duy nhất trên toàn cục tới instance.
– Tác giả đã vô tình kèm cả lời giải trong bài toán với giả định: để có một điểm truy cập toàn cục duy nhất thì chỉ có duy nhất một instance được tạo ra từ một class. Bài toán “một điểm truy cập” có thể được giải quyết bởi Facade, Wrapper… không nhất thiết phải là Singleton. Runtime
– Không “thân thiện” với theading. Design
– Coupling: Vì là global state nên các thành phần bị gắn chặt với nhau.
– Khó / không thể viết test.
– Viết dễ sai sót, để lại lỗ hổng (xem bài trước Singleton có thực sự dễ?).
Nên dùng như thế nào?
Như vậy, ta thấy rằng đa phần những thứ dở của design pattern này là ở tư tưởng global state. Vậy nên những ai yêu thích functional programming thì sẽ rất anti-pattern này. Cũng đúng thôi, GoF sinh ra cho OOP, không phải FP. Và thời đại của GoF (1994) cũng không quan tâm nhiều tới concurrency – bài toán trở thành rất cơ bản trong thời đại này. Bởi vậy, việc sử dụng Singleton có chút thay đổi. Có 3 điều cần chú ý:
Concurrency: Global state là điều tệ hại cho concurrency. Hãy giảm thiểu tối đa nếu có thể. Global state bẻ cong cách suy nghĩ về luồng và gây mệt mỏi cho việc debug trong concurrency. Nếu bạn muốn thiết kế hệ thống tối ưu hiệu năng và concurrency thì không sử dụng Singleton cũng là một ý hay.
Memory:
– Lưu cái gì? Global state cũng là một ý hay vì khiến việc thiết kế và lập trình dễ dàng hơn, nó chỉ không hay khi bạn không cân nhắc tới nên lưu gì. Rất nhiều thứ có thể nhìn dưới góc độ một instance nếu chúng ta không có khả năng khái quát hoá. Logger là Singleton không? Hay có errorlog, accesslog? Database là single thì lưu cả database? Hay chỉ connection? Hay chỉ connectionString? Lưu ít nhất có thể.
– Lưu khi nào? Nhiều người hay gắn Singleton với life cycle của cả ứng dụng, kèm theo việc lưu trữ nhiều, hoặc giữ strong reference dẫn đến GC không thể hoạt động; sớm muộn gì cũng gây ra memory leak. Khởi tạo muộn nhất có thể, giải phóng sớm nhất có thể.
Language: Cần lưu ý cách sử dụng trong từng ngôn ngữ, mỗi ngôn ngữ khác nhau sẽ có vấn đề khác nhau. Dù design pattern là mức thiết kế song đừng mang nguyên cách cài đặt từ ngôn ngữ này sang ngôn ngữ khác, hãy nhìn vào diagram và đặc trưng ngôn ngữ.