Open – Close Principle – Phần 2

Xem bài 1 tại đây: https://daohainam.com/2023/10/24/open-close-principle-phan-1/

Các bạn có thể xem loạt clip về SOLID tại đây: https://www.youtube.com/playlist?list=PLRLJQuuRRcFlRei0t0wbbCdzU8ujTD3jE

Trước hết, vấn đề chúng ta cần phải giải quyết là gì? Đó là tìm ra nghiệm của một phương trình bậc 1 với đầu vào là a và b cho trước, kết quả có thể là vô nghiệm, vô số nghiệm hoặc một x cụ thể nào đó.

Điều tôi muốn nhấn mạnh ở đây chính là mục đích mà chương trình được tạo ra. Thực chất khách hàng của bạn có quan tâm tới việc dữ liệu đầu vào đến từ bàn phím hay từ file không? Không! Họ có quan tâm dữ liệu sẽ được đưa ra màn hình, file PDF hay Excel không? Cũng không! Việc tìm nghiệm mới là mục đích tồn tại của chương trình này, mọi thứ khác diễn ra xung quanh và chỉ nhằm phục vụ cho mục đích này mà thôi.

Khi nhìn vào một bài toán, chúng ta sẽ phải tìm ra những thành phần tham gia vào hệ thống, cái nào quan trọng hơn cái nào, cái nào thường xuyên và cái nào không bao giờ thay đổi… Xác định đúng các thành phần này là bước quan trọng sống còn trong thiết kế, bởi chúng sẽ định hình sự phụ thuộc sau này.

Continue reading “Open – Close Principle – Phần 2”

Open – Close Principle – phần 1

Các bạn có thể xem loạt clip về SOLID tại đây: https://www.youtube.com/playlist?list=PLRLJQuuRRcFlRei0t0wbbCdzU8ujTD3jE

OCP là một trong những nguyên tắc quan trọng trong thiết kế phần mềm, mọi người thường nhớ đến nó như một trong 5 nguyên tắc SOLID. Tuy nhiên thực tế nó đã xuất hiện từ trước đó, được nhiều người thảo luận và sử dụng, ở nhiều cấp độ khác hơn là chỉ các class và interface… Trong bài này chúng ta sẽ cùng tìm hiểu OCP nhé.

Trước tiên, bạn hãy nhìn vào chương trình giải phương trình bậc 1 trong hình, đây là một chương trình rất đơn giản mà ai biết lập trình đều có thể hiểu được (kể cả khi bạn không biết C#). Chương trình này sẽ đọc các số a và b, sau đó trả về kết quả. Giờ ta hãy thử đưa chương trình này cho khách hàng, sau vài năm triển khai, liệu có thể có thêm những yêu cầu gì mới?

Continue reading “Open – Close Principle – phần 1”

Giới thiệu về .NET

Đây là bài viết giới thiệu về .NET, một platform mạnh mẽ, hỗ trợ đa nền tảng, nhiều công cụ hỗ trợ, và cũng rất phổ biến trong thị trường lao động.

👉 Một chút lịch sử:

– Microsoft đã từng hỗ trợ Java. Bộ Visual Studio 6.0 đã từng có một công cụ tên Visual J++ dùng để viết app Java, sau này còn có trình biên dịch Visual J# để dịch ứng dụng viết bằng Java trên .NET.

– Microsoft bắt đầu phát triển .NET (hay còn gọi là .NET Framework) như một nền tảng thay thế Java khi mối quan hệ với Sun Microsystems (chủ sở hữu Java lúc đó) rạn nứt.

– Nhiều ứng dụng của Microsoft đã được viết lại trên .NET, bao gồm cả IDE Visual Studio và CSDL SQL Server, đồng thời trình runtime .NET cũng được tích hợp sẵn vào Windows luôn.

– Tuy về mặt lý thuyết .NET được thiết kế để chạy trên mọi nền tảng, tuy nhiên Microsoft chỉ phát triển bộ runtime cho Windows, do vậy các app .NET chỉ có thể chạy trên Windows.

– Mono project được cộng đồng mã nguồn mở phát triển để xây dựng trình runtime cho .NET trên Linux, và sau này mở rộng ra cả các nền tảng khác. Mono không hỗ trợ đầy đủ các công nghệ như .NET Framework chạy trên Windows.

– Sau một hồi mua đi bán lại thì công ty chủ quản tạo ra Mono có ý đồ dẹp tiệm dự án này, anh chàng LTV đã tạo nên Mono bèn lập ra một công ty mới là Xamarin, tiếp tục phát triển các công nghệ hỗ trợ xây dựng trình runtime cho .NET trên các nền tảng ngoài Windows, thậm chí mở rộng ra cho cả iOS và Android.

– Xamarin phát hành bộ công cụ xây dựng ứng dụng .NET cho đa nền tảng tên là Xamarin Studio.

– Microsoft mua lại Xamarin và mang các công nghệ từ Xamarin Studio tích hợp vào Visual Studio. Một trang sử mới mở ra khi các công cụ này được cung cấp miễn phí thay vì trả phí khá đắt đỏ, người người nhà nhà thi nhau viết app .NET cho smart phone, hơn 1 tỷ developer đã chuyển từ Java/C/C++/Swift sang C# (*).

– Nhận thấy .NET Framework đã trở nên cũ kỹ, đồng thời nhu cầu phát triển ứng dụng trên các nền tảng ngoài Windows (Linux, Mac, iOS, Android…) rất cao. Việc hỗ trợ đồng thời nhiều trình runtime tương thích .NET Framework khác nhau vừa tốn kém chi phí vừa bất tiện, Microsoft phát triển một nền tảng mới gọi là .NET Core.

– .NET Core là một bản .NET được thiết kế và xây dựng lại, với mục tiêu đa nền tảng ngay từ đầu.

– Có rất nhiều dự án vốn viết trên .NET Framework nay muốn chuyển sang .NET Core, tuy nhiên rất khó và cũng rất rủi ro khi chuyển toàn bộ từ nền tảng cũ sang nền tảng mới, vậy nên Microsoft đưa ra thêm một thứ gọi là .NET Standard.

– .NET Standard chỉ là các chuẩn tương thích, không phải là một trình runtime hay framework. Ví dụ .NET Framework 4.6.1+ và .NET Core 2.0+ tương thích với .NET Standard 2.0, do vậy bạn hoàn toàn có thể sử dụng các thư viện được viết ra cho .NET Standard 2.0 trong các chương trình viết cho Framework 4.6.1 lẫn .NET Core 2.0. Nhờ vậy các phần mềm cũ có thể được chuyển đổi dần mà không cần chuyển hoàn toàn sang .NET Core.

– Phiên bản chính cuối cùng của .NET Framework là 4, được phát hành vào 2010, phiên bản hiện tại là 4.8.1, đây cũng sẽ là bản .NET Framework cuối cùng. (**)

– .NET Core có các phiên bản 1, 2 và 3, sau bản 3.0 thì nhảy luôn lên 5. .NET Core không có phiên bản 4 vì sợ nhầm lẫn với .NET Framework 4, vốn là phiên bản phổ biến đến mức trước đây nhiều người hay gọi là .NET 4.0.

– Từ phiên bản 5.0, .NET Core cũng bỏ luôn chữ Core và ta chỉ còn gọi là .NET. Vì không có phiên bản .NET Framework nào >= 5 nên người ta không còn sợ nhầm lẫn nữa.

– Phiên bản chính thức hiện tại là .NET 7, phiên bản 8 sẽ ra mắt vào cuối năm nay. Theo kế hoạch thì cứ một năm sẽ có một phiên bản mới.

– Vì .NET Framework đã ngừng phát triển nên từ giờ trở đi khi nói về .NET, ta sẽ mặc nhiên là nói về .NET mới.

👉 Vậy .NET làm được gì?

– .NET có thể được dùng để viết gần như tất cả các loại ứng dụng, ngoại trừ các ứng dụng hệ thống.

– Bạn có thể viết web, mobile app, ứng dụng desktop, chạy trên local, chạy trên cloud, viết game… Nói tóm lại là trừ khi viết firmware, OS hay driver… còn lại thì bạn đều có thể làm được với .NET.

👉 Có gì lưu ý khi học .NET?

– .NET chỉ là một framework, bạn có thể lập trình bằng bất kỳ ngôn ngữ nào miễn sao nó có trình biên dịch tương thích. Tuy nhiên C# là ngôn ngữ được Microsoft hỗ trợ mạnh nhất và vẫn liên tục được cập nhật, vì vậy học viết app .NET bằng C# được coi là chuẩn mực.

– C# có cấu trúc giống với Java, vậy nên ai đã từng học C++/Java chuyển sang sẽ thấy rất quen thuộc (đó là lý do tại sao tôi luôn khuyên những người mới học nên bắt đầu với C++ – khổ trước sướng sau). Tuy nhiên để nắm chắc C# sẽ mất khá nhiều thời gian vì nó có nhiều thành phần hỗ trợ runtime (async, lock, LINQ…).

– Ứng dụng .NET có thể được dịch trên một nền tảng và chạy trên một nền tảng khác. Chương trình của bạn sẽ được dịch sang mã IL, sau đó sẽ được dịch tiếp một lần nữa sang mã máy khi chạy (JiT), các bản .NET mới còn cho phép dịch sẵn sang mã máy khi cài đặt ứng dụng (AoT), nhờ đó tốc độ khởi động ứng dụng sẽ nhanh hơn.

– Bộ nhớ trong .NET được quản lý tự động, bạn không cần giải phóng vùng nhớ đã cấp phát.

– Tôi khuyên dùng Visual Studio bản mới nhất nếu có thể, bạn sẽ mất một thời gian làm quen nhưng khi đã quen rồi thì bạn sẽ hiểu vì sao nó là IDE hàng đầu thế giới. Dù gì sẽ có lúc bạn phải làm quen với nó nếu theo .NET.

– .NET (Core) là mã nguồn mở, .NET Framework cũng mở mã nguồn nhưng chỉ cho mục đích tham khảo.

👉 Có nhiều công ty tuyển dụng .NET không?

Bạn tự vào các trang tuyển dụng tìm hiểu đi! Ở đây không trả lời mấy câu này!

MVC design pattern và cách chúng ta hỗ trợ trong Mini-Web-Server – phần 2

MVC là gì?

Model-View-Controller là một mẫu thiết kế cho phép ta phân tách các thành phần theo 3 chức năng:

  • Các thực thể (entity) chứa dữ liệu, hay còn gọi là Model.
  • Các View có nhiệm vụ hiển thị dữ liệu.
  • Các Controller có nhiệm vụ điều phối Model đến View.

Việc phân tách này giúp chúng ta tách biệt các thành phần với các chức năng hoàn toàn độc lập, và mang lại những lợi ích sau:

  • Dễ thay đổi: Một thay đổi nào đó trên giao diện chỉ ảnh hưởng đến View. Giả sử chương trình của bạn cần cung cấp các trang hiển thị thông tin sản phẩm khác nhau: cho người dùng web, cho người dùng trên các thiết bị màn hình nhỏ, cho máy in… thì chỉ cần tạo ra các view khác nhau, tất cả những gì trong Model hoặc code tải thông tin sản phẩm trong Controller đều không cần thay đổi.
  • Dễ test: bạn hoàn toàn có thể viết các unit test riêng biệt cho Controller (chứa business logic) hoặc View (để test giao diện), một tester cũng có thể test các view thông qua các Fake/Mock object mà không cần chờ Controller hoàn thiện.
Continue reading “MVC design pattern và cách chúng ta hỗ trợ trong Mini-Web-Server – phần 2”

MVC design pattern và cách chúng ta hỗ trợ trong Mini-Web-Server – phần 1

Loạt bài này sẽ nói về MVC design pattern, cách chúng ta thiết kế các thành phần để hỗ trợ MVC trong Mini-Web-Server.

Mô hình MVC, lưu ý hướng các mũi tên, các bạn có thể sẽ nhìn thấy nhiều hình minh họa khác với số lượng/hướng mũi tên khác nhau. Tôi sẽ giải thích kỹ hơn về điều này khi chúng ta đi vào chi tiết.

Nếu theo dõi blog này và trang Facebook Nam.NET chắc mọi người không lạ gì project Mini-Web-Server (trang github), một web server đơn giản để thông qua đó giới thiệu về những công nghệ, mẫu thiết kế, kiến trúc phổ biến, giúp các bạn hiểu sâu hơn về những gì các bạn đang sử dụng hàng ngày. Hiện tại chúng ta đã có một mini server gọn nhẹ, chạy nhanh và hỗ trợ đầy đủ các method, hỗ trợ HTTPS, các nội dung tĩnh, cho phép viết các hàm phục vụ người dùng giúp phát triển các API, cung cấp đầy đủ session, authentication/authorization thông qua JWT và cookie…

Nhưng điều quan trọng nhất mà chúng ta đạt được (cũng như tôi muốn nhấn mạnh) khi viết Mini-Web-Server không phải là nó đã hỗ trợ những gì, mà lại là việc thêm vào những tính năng mới dễ dàng như thế nào. Thông qua một kiến trúc rõ ràng và nhiều design pattern, chúng ta có một ứng dụng với các module độc lập với nhau, gắn kết với nhau thông qua các policy. Khi thay thế module phân tích các request từ sử dụng Regular Expression sang việc phân tích trực tiếp mảng byte, chuyển từ mô hình sync sang async…, chúng ta hầu như không phải làm gì khác ngoài viết module mới và thay thế với module cũ, tất cả các phần khác đều giữ nguyên. Thông qua một loạt các cơ chế dựa trên “Dependency Inversion” như Callable, Callable filters, middleware… ta có thể mở rộng ra tới chừng nào ta muốn. Và đó là thứ quan trọng nhất mà tôi muốn mang đến cho các bạn thông qua project này.

Tất cả các ứng dụng trong thực tế đều sẽ thay đổi theo thời gian, lớn hơn, nhiều yêu cầu mới phát sinh, mô hình triển khai thay đổi và thậm chí các chức năng cũng thay đổi, do vậy việc quan trọng nhất đối với một người thiết kế đó là làm ra một ứng dụng có thể dễ dàng chỉnh sửa và bảo trì, hơn là làm cho nó chạy đúng. Điều này nghe có vẻ buồn cười nhưng thử nghĩ xem, nếu chỉ để một chương trình chạy đúng (vào một thời điểm cụ thể) thì bạn đâu cần phải thiết kế? Bạn không cần các interface, không cần đa hình, khi thêm một chức năng mới, bạn chỉ việc copy từ một chức năng tương tự đã có và sửa lại một chút… Bạn sẽ có những file mã nguồn, những function hàng ngàn dòng mà đến người viết còn không hiểu nó chạy thế nào, và khi có một yêu cầu mới, bạn chỉ mong ước có thể đập ra làm lại từ đầu. Một chương trình dễ bảo trì sẽ dễ dàng được làm cho chạy đúng, nhưng một chương trình chạy đúng với một thiết kế tồi lại không chắc sẽ chạy đúng mãi (sau khi thêm các chức năng mới hoặc thay đổi trong tương lai).

Quay lại với Mini-Web-Server, chúng ta đã hỗ trợ nhiều tính năng (như quảng cáo :D), nhưng những gì các bạn nhìn thấy chỉ là một trang web đơn giản, với vài tính năng demo. Thực sự mà nói, chừng đó cũng đã đủ để bạn viết một ứng dụng web, với nội dung tĩnh trong các trang HTML, và dữ liệu động được cung cấp bởi các API. Các tính năng cơ bản này cho phép bạn viết các ứng dụng SPA (single page app) dễ dàng, hoặc cung cấp backend API cho một mobile app. Nhưng nếu muốn viết một web app bình thường với nhiều trang thì sao? Có lẽ chúng ta cần thêm một cơ chế khác nữa.

Một lựa chọn phổ biến với người viết web .NET là ASP.NET MVC, một framework cực kỳ mạnh mẽ, hỗ trợ nhiều tính năng và cực kỳ mềm dẻo (flexible), cho phép chúng ta phát triển các ứng dụng với code .NET, render nội dung với Razor, các tham số được tiền xử lý và gửi đến các action, người lập trình hầu như chỉ cần tập trung vào việc viết code xử lý, các request/response sẽ được xử lý tự động thông qua các policy được ASP.NET hỗ trợ… Vì vậy nếu Mini-Web-Server cũng hỗ trợ một mô hình như vậy thì các nhà phát triển sẽ dễ dàng xây dựng các ứng dụng dựa trên “Mini MVC” thay vì ASP.NET MVC :D. Và có lẽ đây sẽ là tính năng lớn nhất mà Mini cung cấp, là một hành trình mất nhiều tháng để hoàn thành, và tôi muốn chia sẻ cùng các bạn hành trình này, từ các bước chúng ta thiết kế, cho đến việc implement, và thậm chí những sai sót khi thiết kế đã dẫn đến những vấn đề gì. A ha! Bạn nghĩ đúng rồi đó! Tôi vẫn thường xuyên sai sót khi thiết kế và cả khi code nữa!

Nhưng trước tiên chúng ta phải hiểu một cách chính xác MVC là gì và nó hoạt động như thế nào.

(hết phần 1)

Authentication, Authorization và hỗ trợ xác thực trong Mini Web Server – part 1

Nếu đã bắt tay vào một phần mềm nào đó dù nhỏ thì chắc các bạn cũng đã nghe tới hai khái niệm Authentication và Authorization. Trong bài này ta cùng tìm hiểu và xem cách chúng ta hỗ trợ hai tính năng này trong Mini Web Server thế nào nhé.

Authentication và Authorization là gì?

Authentication là hành động xác định danh tính của một người dùng, nó dùng để trả lời câu hỏi “Người dùng đang truy cập là ai?”. Một trong những phương pháp phổ biến nhất mà chắc ai cũng biết là sử dụng user name/password, nhưng cũng có nhiều phương pháp khác, hỗ trợ cho các usecase khác, như sử dụng các token, client certificate, OTP… Những usecase này có một số quy trình hoặc yêu cầu khác biệt, ví dụ:

  • Các API không có giao diện nên không thể yêu cầu người dùng đăng nhập bằng tên đăng nhập hoặc mật khẩu.
  • Một số dịch vụ yêu cầu đặc biệt về bảo mật cần người dùng cài đặt trước các certificate trên máy client, do vậy bạn chỉ có thể đăng nhập từ một máy tính cụ thể.
  • OTP cho phép bạn đăng nhập dễ dàng mà không cần nhớ mật khẩu…
  • Các hard token lưu trữ khóa đăng nhập trong một thiết bị phần cứng giúp bạn đăng nhập một cách bảo mật nhưng vẫn dễ dàng.

Một số hệ thống cho phép bạn chọn một trong những phương pháp xác thực trên, hoặc thậm chí kết hợp chúng lại với nhau, ví dụ bạn phải đăng nhập bằng user/password trên một máy tính có gắn hard token, hay nhập mã OTP sau khi nhập password…

Nhiều phần mềm hiện tại sử dụng một hệ thống thứ ba đảm nhiệm việc xác thực người dùng, từ đó cho phép ta xây dựng mô hình single sign-on (SSO), dùng một tài khoản để truy cập vào nhiều phần mềm khác nhau. Nhờ vậy người phát triển ứng dụng được giải phóng khỏi các tính năng liên quan đến xác thực, vốn cũng rất phức tạp và tốn kém chi phí phát triển lẫn duy trì. Người dùng cũng được giải phóng khỏi việc ghi nhớ nhiều tên đăng nhập/password trên các hệ thống khác nhau, từ đó giúp cho việc bảo mật cũng trở nên dễ dàng hơn. Một ví dụ là hiện có rất nhiều web site cho phép đăng nhập bằng tài khoản Facebook, Google, Apple…

Một phần mềm tốt phải được thiết kế sao cho không phụ thuộc vào phương thức xác thực, nhờ vậy khi có yêu cầu mới về hệ thống, bạn sẽ dễ dàng thay đổi mà không sợ ảnh hưởng đến phần core (phần xử lý các quy trình kinh doanh của hệ thống).

Authorization là hành động xác định quyền hạn mà một người dùng được phép thực hiện. Nôm na là sau khi đã biết “Người dùng đang truy cập là ai?”, ta sẽ trả lời tiếp “Người dùng này được làm những gì”. Tuy Authentication và Authorization là hai hành động riêng biệt, tuy nhiên do mối quan hệ tự nhiên của chúng mà ta luôn nhắc đến chúng như thể là một.

Khác với Authentication, Authorization thông thường được định nghĩa và quản lý bởi hệ thống của bạn, vì các tính năng mỗi hệ thống cung cấp khác nhau, do vậy chỉ có bạn biết chính xác nên quản lý chúng như thế nào. Khi nói đến authorization, ta thường quen với các khái niệm như permission, group, hay role… chúng giúp ta định nghĩa các quyền cho một người dùng cụ thể, hoặc một nhóm người dùng có cùng vai trò.

HTTP hỗ trợ Authentication và Authorization như thế nào?

Khi truy cập vào một tài nguyên được bảo vệ và yêu cầu xác thực, server sẽ trả về lỗi 401 .Unauthorized, đồng thời trả về một header có tên WWW-Authenticate chứa các scheme mà server hỗ trợ, client sẽ dựa vào các scheme này để chuyển đến trang phù hợp cho phép người dùng thực hiện đăng nhập (xác thực).

Với việc xác thức, giao thức HTTP hỗ trợ một header có tên Authorization, cú pháp của header này khá đơn giản:

Authorization: <auth-scheme> <auth-parameters>

Trong đó auth-scheme chỉ ra scheme – cách dữ liệu người dùng đang truy cập được mã hóa (encode) trong auth-parameters. Web server (hoặc web app) đọc header này và giải mã (decode) các tham số trong auth-parameters để định danh người dùng và xác định các quyền mà người dùng có.

Scheme đơn giản nhất là Basic, khi đó auth-parameters sẽ là một chuỗi ghép từ username:password, sau đó được encode dạng Base64. Dễ dàng nhận thấy rằng nếu request bị bắt trên đường truyền, ta có thể đọc lại được username và password một cách dễ dàng, vì vậy thông thường ta chỉ dùng Basic trên các kết nối HTTPS. Thực tế là hiện tại hiếm có website nào còn dùng scheme này vì nó (trông có vẻ) ít bảo mật, và trải nghiệm đăng nhập không tốt.

Một scheme quan trọng được dùng rất phổ biến khi viết các Web API là Bearer, khi đó auth-parameters là một Json Web Token (JWT).

Ta có thể thấy, mỗi khi truy cập vào một tài nguyên được bảo vệ, ta luôn phải gửi kèm Authorization header, và server buộc phải xác thực header này trước khi thực hiện các bước tiếp theo. Nếu sử dụng Basic scheme như đã nói ở trên, server cần làm gì để xác thực? Có lẽ sẽ là kết nối đến database và kiểm tra xem có cặp username/password nào phù hợp không. Đây là một phương thức tốn kém chi phí, sử dụng nhiều tài nguyên server, thậm chí có thể còn tốn kém hơn bản thân chức năng mà API cung cấp.

Các API cũng được thiết kế để phục vụ các chương trình máy tính, chúng đòi hỏi khả năng xử lý lớn hơn nhiều so với các trang web (vốn được thiết kế để phục vụ con người). Bearer cho phép chúng ta xác thực mà không cần kết nối đến cơ sở dữ liệu, bằng cách chứa thông tin cả về user lẫn Security Service Provider (SSP), và cả chữ ký (digital signature) của nó. HTTP Server chỉ cần xác thực chữ ký thành công là có thể lấy được thông tin về user, không có bất kỳ thao tác nào lên cơ sở dữ liệu người dùng (mà thật ra cũng không cần có CSDL này).

Vậy tới đây ta đã biết JWT được dùng làm gì và khi nào, tuy nhiên một vấn đề khác lại xảy ra. Đó là vì server không kết nối đến DB để kiểm tra, vậy nên có thể xảy ra trường hợp tài khoản người dùng không còn hợp lệ sau khi token (JWT) đã được sinh ra, server sẽ không nhận ra và vẫn chấp nhận token. Để giải quyết vấn đề này, người ta đặt tuổi của token ngắn lại, thường thì chỉ tính bằng phút, khi hết hạn người dùng sẽ phải xác thực lại để lấy về token mới với thông tin đã được cập nhật. Nhưng từ đây lại phát sinh ra một vấn đề khác, nếu cứ mỗi vài phút lại phải quay lại trang đăng nhập một lần thì quá phiền phức, nên người ta cấp thêm một token thứ hai với vòng đời dài hơn (thường tính bằng ngày), mỗi khi token thứ nhất hết hạn, ta sẽ gửi token thứ hai lên cho server để xác thực, khi đó server mới kiểm tra và cấp lại một token mới cho client, người dùng chỉ cần xác thực lại nếu quá trình xác thực token thứ hai không thành công.
Token thứ hai này thường được gọi với cái tên là refresh token, và không nhất thiết phải là một JWT.

(Ảnh minh họa từ Mozilla Developer Network)

Hỏi đáp về Mini-Web-Server

Q: Mini-Web-Server là gì?

A: Mini-Web-Server (gọi tắt là Mini) là một web server mã nguồn mở viết trên C#, .NET 7, hỗ trợ HTTP/1.1, HTTPS, Multi-Host…

Q: Mini có hỗ trợ nội dung động không?

A: Có, vì không có nội dung động thì sẽ không cần dùng đến các method như POST, PUT… Tuy nhiên hiện tại tôi chưa quyết định cụ thể sẽ hỗ trợ như thế nào, ban đầu dự định hỗ trợ thông qua CGI (https://www.hostgator.com/help/article/what-is-cgi-bin), thông qua đó sẽ dễ dàng tích hợp các ngôn ngữ script như JS hay PHP, nhưng CGI đã quá cũ và tiềm ẩn rất nhiều rủi ro về bảo mật nên có thể tôi sẽ tự thiết kế một engine riêng. (Ở đây các bạn có thể thấy sức mạnh của abstraction, khi tôi có thể thiết kế web server trước khi implement phần cung cấp nội dung).

Q: Mini được viết ra để làm gì?

A: Mini được viết ra như một ứng dụng minh họa cho những khái niệm/công nghệ mà tôi đã/đang/định/sẽ nói đến. Do vậy một trong nhưng tiêu chí khi thiết kế và implement là code phải dễ đọc dễ hiểu nhất.

Tôi không thích lắm khi nói về OOP cứ phải lấy ví dụ về Animal với Dog với Cat, hoặc là mấy cái hình vuông với hình chữ nhật, nghe thì dễ hiểu nhưng người học chẳng biết dùng vào việc gì cả.

Các bạn sẽ được nhìn thấy các phần đã học được áp dụng trong thực tế thế nào. Có những khái niệm khá mơ hồ và khó hiểu (ai học OOP thì chắc cũng biết), nhưng vì máy tính là khoa học, không phải nghệ thuật, nên dù có khó thì cũng chỉ có một cách hiểu duy nhất.

Thông qua việc viết ra một ứng dụng hoàn chỉnh, chúng ta sẽ hiểu rõ các khái niệm này một cách chính xác, bằng cách so sánh các cách áp dụng khác nhau (vì hiểu theo những cách khác nhau) sẽ dẫn đến những kết quả nào.

Q: Tại sao không phải là ứng dụng khác mà lại là một Web server?

A: Một web server hoàn chỉnh dù nhỏ cũng phải chứa khá nhiều tính năng khác nhau, và phải sử dụng nhiều kỹ thuật, thiết kế, kiến trúc. Ngoài ra bạn sẽ phải đi sâu vào giao thức HTTP, nền tảng cho mọi thứ trên web, kể cả các web app hay API. Do vậy theo tôi nó là hoàn hảo để trình bày các kỹ thuật khác nhau khi viết chương trình.

Q: Mini có thể thay thế các web server phổ biến hiện tại không?

A: Về mặt chức năng: Có. Nó sẽ có khả năng phục vụ để tạo ra các website, API, (và

có thể cả websocket).

Về hiệu quả sử dụng: Không (hoặc chí ít là chưa). Bạn không nên mạo hiểm đưa mini vào sử dụng production 😅. Một dự án nho nhỏ làm trong lúc rảnh rỗi thì không thể so được với dự án được hàng trăm chuyên gia làm trong nhiều (chục) năm.

Tuy nhiên, sử dụng Mini làm backend server và một web server khác (nginx chẳng hạn) làm reverse proxy cũng có thể là một ý hay 🙂.

Q: Tại sao không sử dụng các web server mã nguồn mở để học?

A: Hầu hết các web server phổ biến nhất (trừ IIS) đều là mã nguồn mở. Bạn hoàn toàn có thể đọc code để xem họ đã làm như thế nào. Tuy nhiên bạn chỉ nên làm điều này khi muốn tham gia vào dự án, bởi sau nhiều năm phát triển chúng đã quá phức tạp để hiểu, đặc biệt với người chưa có kinh nghiệm.

Minix – hệ điều hành còn già hơn Linux (Minix chính là cảm hứng để Linus Torvalds viết lên Linux) vẫn còn được dùng để giảng dạy môn Hệ điều hành trong nhiều trường đại học (https://wiki.minix3.org/doku.php?id=courses) bởi sự đơn giản của nó (Linux hiện đã có hơn 30 triệu dòng code).

Q: Mini có hỗ trợ HTTP/2, HTTP/3 không?

A: Có (nhưng khi nào thì chưa biết 😅). HTTP/2 và HTTP/3 chủ yếu khác HTTP/1.1 ở phương thức truyền dữ liệu giúp tăng cường hiệu năng, cấu trúc các request và response và các tính năng bên dưới hầu như không có thay đổi. Do vậy việc implement hai phiên bản này không phải là một yêu cầu quan trọng với một máy chủ web “hiệu năng thấp” như Mini.

Q: Tính năng Multi-Host là gì?

A: Multi-Host cho phép bạn chạy nhiều website trên cùng server, tùy thuộc vào domain name.

Q: Làm sao tôi hiểu được Mini được thiết kế và viết như thế nào?

A: Tôi sẽ có bài viết/video hướng dẫn, hiện nay chưa làm là vì LƯỜI!

Q: Nếu một lúc nào đó ứng dụng được hoàn thành, vậy khi đó nó lại trở nên quá phức tạp để học?

A: Khi đã implement các tính năng ở một mức độ hoàn chỉnh cho phép, tôi sẽ tạo các tag để lưu lại source code ở thời điểm đó (https://github.com/daohainam/mini-web-server/tags). Các bài hướng dẫn sẽ dựa trên các tag này, tránh việc khi source code thay đổi thì nội dung bài viết trở nên không chính xác. Khi nói về vấn đề nào, tôi sẽ cố gắng sử dụng tag đầu tiên mà tính năng đó xuất hiện, giúp chúng ta dễ dàng theo dõi nhất.

Q: Viết ra Mini có khó không?

A: Khó. Đặc biệt là phần xử lý giao thức HTTP. Dù tôi đã làm với HTTP từ rất lâu (có thể từ trước khi một số độc giả ở đây ra đời 😉) nhưng tôi vẫn thấy khó, nguyên nhân là vì phải đọc và implement TOÀN BỘ giao thức, theo đúng các RFC của nó (https://datatracker.ietf.org/doc/html/rfc9112), không được bỏ sót kẽ hở nào (công nhận ông này cũng rảnh thật). Ngoài ra việc xử lý các dữ liệu đó trên nhiều stream khác nhau một cách không đồng bộ cũng gây ra khó khăn, bởi sẽ xảy ra vô số trường hợp cần xử lý.

Q: Để hiểu thì có cần biết kiến thức gì trước không?

A: Tất nhiên là có. Bạn sẽ cần biết về lập trình, tốt nhất là với C#, nhưng nếu chỉ biết một ngôn ngữ khác tương tự cũng không sao (Java, C++), nếu chỉ biết JS thì hơi khó nhưng nếu đã viết app NodeJS thì chắc cũng được.

Bạn sẽ cần biết về networking (TCP/IP, socket), OOP, multi-thread programming, các cấu trúc dữ liệu thông dụng… Những thứ này các bạn có thể đọc các bài viết trên trang https://www.daohainam.com hoặc hỏi chị Google. Khi đi vào phần code, sẽ có nhiều pattern (Mini được viết để minh họa cho các pattern này), sẽ dễ hiểu hơn nếu các bạn đã từng học về chúng.

Ngoại trừ phần ngôn ngữ lập trình, các phần còn lại sẽ có bài viết/video giải thích. Hiện nay vì sao chưa có chắc các bạn cũng biết rồi đấy! Vì LƯỜI! 😁

Giới thiệu project mới

Trong bài viết “NHỮNG MÔN HỌC CẦN THIẾT NẾU BẠN MUỐN TRỞ THÀNH SENIOR DEVELOPER“, tôi đã nói về những môn học giúp bạn có kiến thức vững chắc, và tự tin tham gia vào bất kỳ dự án nào. Tuy nhiên sau khi đọc xong, có nhiều bạn nhắn với tôi là “anh ơi, em biết là phải học những môn đó, nhưng em lại không biết học như thế nào”. Thật sự điều này cũng tương đối khó, bởi có quá nhiều thứ để học, bạn không biết được mình phải bắt đầu từ đâu, lộ trình thế nào, làm sao đạt được hiệu quả tốt nhất, thời gian ngắn nhất.

Một trong những cách học hiệu quả nhất là bắt tay vào làm một dự án thực tế (tôi cũng có nói về điều này trong bài Một số lời khuyên cho người học IT), nhưng làm cái gì lại là một câu hỏi khác. Một trong những sai lầm của các bạn khi chọn đề tài là các bạn chọn một dự án mà bạn đã biết cách làm, điều này làm bạn mất thời gian nhưng không đạt hiệu quả. Ví dụ khi chọn đề tài là viết một hệ thống quản lý điểm sinh viên, bạn sẽ nhận ra rằng có rất nhiều thao tác trong đó chỉ là CRUD (tạo/xem/xóa/sửa) dữ liệu trong database, và thao tác bạn làm nhiều nhất đôi khi lại là cắt/dán.

Sau một hồi suy nghĩ về điều này, tôi đã chọn ra một đề tài có thể giúp các bạn vừa học OOP, design pattern (Abstract Factory, DI, IoC, Decorator, caching patterns, và nhiều nhiều nữa…), vừa liên quan đến networking (TCP/IP, HTTP), lại có cả lập trình multithread.

Đó là viết ra hẳn một Web Server.

Continue reading “Giới thiệu project mới”

Những dấu hiệu cho thấy code của bạn có vấn đề (2) – Data Clump

Nếu có nhiều dữ liệu liên quan với nhau và thường phải làm việc cùng nhau, hãy gom chung vào một lớp thay vì lưu trong nhiều biến riêng biệt.

Một ví dụ là các tham số cho một cơ sở dữ liệu, bao gồm: địa chỉ server, user name, password, port. Mỗi khi kết nối, bạn đều phải cần cả 4 tham số này, do vậy thay vì lưu chúng trong 4 biến khác nhau, hãy gom chúng vào chung một lớp. Nếu có một function dùng các tham số này, ví dụ:

bool Connect(string serverAddress, int port, string user, string password);

Hãy sử dụng lớp bạn vừa tạo:

bool Connect(DatabaseConnectionInfo connectionInfo);

Sau khi refactor, code của bạn sẽ được tổ chức gọn gàng, dễ hiểu hơn. Tuy nhiên là khi tạo một lớp mới, cần lưu ý không để tăng sự phụ thuộc giữa các lớp hoặc thư viện.

Những dấu hiệu cho thấy code của bạn có vấn đề – Bài 1

Với một coder, viết code nhanh, đúng, chưa đủ mà bạn còn phải viết đẹp và dễ đọc nữa, vì nó sẽ giúp code dễ bảo trì hơn. Trong công việc hàng ngày, đôi khi việc tìm và sửa lỗi còn mất thời gian hơn viết mới nữa, vì bạn có thể sẽ phải đọc lại những đoạn code do người khác viết, hoặc thậm chí do chính bạn viết, từ trước đó rất lâu (thậm chí có thể trước khi bạn biết dùng máy tính :D).

Hãy thử đọc lại một chương trình bạn viết khi mới vào nghề xem, nếu nó cũ hơn 3 năm, tôi tin chắc 99% bạn sẽ thấy nó rất … buồn cười :D.

Lời khuyên đầu tiên của tôi là: Ngay từ ngày đầu tiên, hãy học cách viết code rõ ràng, sáng sủa, kể cả khi bạn biết chắc sẽ không ai đọc nó. Hãy biến nó thành thói quen và phải cảm thấy khó chịu khi nhìn vào một một đoạn “dirty code”.

Trong bài này, tôi sẽ nói qua những dấu hiệu cần tránh, giúp chương trình của bạn sẽ đọc, dễ bảo trì hơn. Có rất nhiều dấu hiệu như vậy, do đó tôi sẽ chia chúng ra nhiều bài ngắn giúp bạn dễ theo dõi.

Continue reading “Những dấu hiệu cho thấy code của bạn có vấn đề – Bài 1”