Showing posts with label N-Tier. Show all posts
Showing posts with label N-Tier. Show all posts

Viết ASP.NET bằng MVP và NHibernate phần 2 - Implement Data Layer

Bài viết này được dịch, tóm tắt và bổ sung dựa vào bài viết NHibernate Best Practices with ASP.NET, 1.2nd Ed trên code Project. Các code sample trong bài dựa vào database Northwind của Microsoft và tham khảo 99,99% từ code mẫu của tác giả Billy McCafferty.

I. Nhắc lại kĩ thuật Separated Interface


- Separated Interface là một kĩ thuật trong lập trình nhằm đạt được mục tiêu phân chia rạch ròi sự phụ thuộc giữa các tầng (tiers) trong chương trình. Kĩ thuật này được giới thiệu lần đầu bởi sư phụ Martin Fowler, nó cũng là một trong những nguyên tắc lập trình nổi tiếng được nhắc đến trong cuốn sách Agile Software Development của Robert Martin. Trong thực tế, kĩ thuật này thường được áp dụng khi người ta implement Domain Layer và Data Access Layer. Có một ví dụ thế này, trong domain layer ta tạo một lớp Customer. Lớp Customer này sẽ có nhiều method, một trong số đó là method GetAllCustomer(). Vậy lớp Customer cần phải sử dụng một data access object để xử lý yêu cầu đó. Kết quả là đối tượng Customer ít nhất đã có một sự phụ thuộc nhất định đến CustomerDao (nằm trong Data Access Layer). Mặt khác đối với lớp CustomerDao, nó sẽ trực tiếp truy xuất vào database và trả về kết quả là một mảng/list các đối tượng kiểu Customer, vì thế nó phải phụ thuộc ngược lại Domain Layer vì như chúng ta đã thống nhất với nhau mình sẽ khai báo các class Entity trong Domain Layer (Project EnterpriseSample.Core). Nếu chưa/không biết kĩ thuật này, có thể chúng ta có thể dùng cheat code bằng cách tách tất cả những lớp định nghĩa Entity ra một project khác ví dụ như EnterpriseSampe.Entities. Còn nếu đã biết Dependency Inversion Priciple rồi thì ta sẽ làm một cách sạch sẽ và trong sáng hơn nhiều :bbpraroi:.

- Có một giải pháp còn cheat ghê hơn nữa:bbpcuoi3: là đưa tất cả các lớp DAO, nói chung là mọi thứ trong Data Access Layer vào chung một assembly với Domain Layer, tức là vào chung project EnterpriseSample.Core. Và để các lớp này khỏi chung chạ với nhau, ta phải tạo ra một sự độc lập ảo bằng cách cho chúng vào những folder khác nhau trong project chẳng hạn như folder Domain và folder Data. Cách làm này sẽ gây ra những vấn đề sau đây:

+ Domain layer và Data layer sẽ phụ thuộc hai chiều vào nhau.
+ Ở chung một nhà lại phụ thuộc nhiều thế này thì lâu ngày … sẽ có con chung :bbpxtay:. Code của 2 layer này sẽ trùng lặp với nhau không chừng.
+ Nếu trong code của Entity object sử dụng trực tiếp một instance của class DAO thì sẽ rất khó Unit Test nó mà không cần database thực. Vì khó test nên có thể người ta sẽ lười test, mà lười rồi thì khỏi viết Unit Test :bbpraroi:.

- Thực ra cách mà tác giả đề nghị là domain và data layers nên được đặt trong các assembly độc lập. Chẳng hạn như XXXX.Core và XXXX.Data. Domain layer (assembly XXXX.Core) sẽ chứa những lớp domain và DAO Interface. Còn Data Layer (assembly XXXX.Data) chỉ chứa những lớp DAO, những lớp DAO này là những lớp sẽ implement các Interface nói trên trong domain layer. Các bạn hãy xem lại hình vẽ cấu trúc này lần nữa:
ASP.NET, NHibernate và MVP

Hình 1: Quan hệ giữa project Core và project Data
Các bạn đang xem bài viết ASPNET bằng MVP và NHibernate phần 2 từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)

- Cách làm trong sáng này có một số lợi ích sau:
+Domain Layer sẽ hoàn toàn độc lập với các assembly liên quan đến database như NHibernate hay System.Data.SqlClient
+Domain Layer không cần quan tâm xem data layer hoạt động thế nào, truy xuất loại database gì… Vì thế việc chuyển đổi các loại database khác nhau cũng như thay đổi chi tiết implementation của tầng data layer rất dễ dàng và không hề ảnh hưởng đến code của domain layer.
+ Vì hai layer này quan hệ một chiều với nhau như vậy nên những fan của Unit testing có thể sử dụng các kĩ thuật “Mocked” data-access layer trong domain layer để test các lớp Entity mà không cần có database thực.

II. Khai báo các Interface cần thiết


- Bây giờ chúng ta sẽ từng bước khai báo các interface này. Trước hết là interface IDao, đây là một interface chung nhất khai báo các function cơ bản nhất mà bất cứ một data access object nào cũng có, nội dung interface này như sau::bbpraroi:
ASP.NET, NHibernate và MVP

Code 1: Khai báo các function chung trong interface IDao

- Đây là 1 generic interface với hai kiểu generic là T và IdT. Nếu ta nhìn phần khai báo các function thì cũng đoán ra nếu một lớp DAO nào implement interface này hoặc một interface nào inherit từ interface này sẽ phải khai báo kiểu của Entity và kiểu Id của Entity đó. Ví dụ như hai interface ICustomerDao và ISupplierDao như sau:
ASP.NET, NHibernate và MVP

Code 2: Các interface kế thừa từ Generic interface IDao

- Vậy đến khi implement thực sự các Data Access Object, ta sẽ implement các interface như ICustomerDao, ISupplierDao. Ở phần 1, chúng ta đã tạo 1 lớp là HistoricalOrderSummaryDao để giữ giá trị khi gọi Stored Procedure. Vì đây là một “value class“ đặc biệt, không phải là một Entity chính thức nên nó không cần Id; do đó interface DAO tương ứng cũng không cần phải kế thừa từ interface IDao:
ASP.NET, NHibernate và MVP

Code 3: Interface IHistoricalOrderSummaryDao không cần kế thừa từ IDao
Các bạn đang xem bài viết ASPNET bằng MVP và NHibernate phần 2 từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)

- Cuối cùng chúng ta sẽ tạo một interface IDaoFactory, nếu các bạn đã đọc về Factory Pattern thì trong trường hợp này chúng ta đang sử dụng Abstract Factory. Sẽ có 1 lớp Factory implement interface này và tùy chúng ta sử dụng ORM nào thì các lớp DAO tương ứng sẽ được tạo ra. Cho nên trong phần sau chúng ta sẽ viết 1 lớp NHibernateDaoFactory với mục đích tạo ra các NHibernate Dao Object::bbpcuoi5:

Code 4: Nội dung của interface IDaoFactory

III. Làm việc với NHibernate Session


- Khi viết chương trình có sử dụng database, người ta thường tìm cách giới hạn số lần mở connection đến database server sao cho càng ít kết nối càng tốt. Nếu các bạn quan tâm đến vẫn đề này hẵn các bạn sẽ biết đến khái niệm Transaction. Trong NHibernate cũng có một khái niệm như vậy và chúng ta hãy tạm chấp nhận một NHibernate Session là một connection đến database server bằng NHibernate. Khi viết một ứng dụng web, việc giảm thiểu các connection đến database là điều rất đáng để quan tâm. Giả sử chúng ta viết 1 trang aspx trên trang đó có sử dụng 10 usercontrol, mỗi usercontrol lại cần kết nối với database để load dữ liệu riêng của nó. Như vậy một lần mở trang web ta đã mở 10+ connection đến database server. Chúng ta có thể giải quyết vấn đề nhức đầu này bằng cách đưa tất cả các request đó vào cùng một transtaction. Cụ thể thế nào thì chúng ta sẽ bàn vào bài sau, còn trong phạm vi bài này chúng ta hãy chấp nhận rằng mỗi một Dao object khi cần kết nối đến database để đọc/ghi dữ liệu nó sẽ cần đến một NHibernate.ISession. Và tác giả đã implement một lớp NHibernateSessionManager cho chúng ta sử dụng, đối số cần truyền cho method GetSesssionFrom() là một giá trị string chứa đường dẫn đến file config của NHibernate. Cách làm này của tác giả có rất nhiều mục đích như: hỗ trợ truy xuất nhiều database một lúc và sử dụng một kĩ thuật gọi là OpenSessionInView để quản lý connection đến database server,.. Tác giả đã implement các lớp Configuration để hỗ trợ đọc các file config NHibernate vì các config này được đặt vào một file xml riêng và không còn nằm chung trong file web/app.config. Các lớp này được viết một lần nhưng ta có thể sử dụng chúng cho nhiều project khác nên chúng được đặt trong một assembly riêng tên là ProjectBase.Data. Chúng được đặt trong thư mục NhibernateSessionMgmt; nếu các bạn quan tâm có thể tìm hiểu khái niệm Configuration của .NET Framework 2.0
Sau đây là một ví dụ có sử dụng NHibernate session:

Code 9: Sử dụng NHibernate Session

IV. Implement DaoFactory và các lớp Dao tương ứng


- Vậy là cơ bản chúng ta đã hoàn thành xong Domain Layer. Xin nhắc lại project EnterpriseSample.Core chỉ chứa các lớp Entity và các interface Dao, không chứa bất kì một lớp Dao nào. Ngay sau đây chúng ta sẽ tạo các lớp Dao này trong project EnterpriseSample.Data:

IV.1 Generic DAO


- Các lớp Dao là những lớp trực tiếp sử dụng NHibernate để thực hiện các lệnh thêm xóa, sửa, update vào database. Ví dụ ta có CustomerDao sẽ implement các function như UpdateCustomer, GetAllCustomers. Nhìn chung thì mỗi một Entity trong chương trình đều có các method tương tự nhau như: GetById, Save, Delete, Update. Thử tưởng tượng bạn có 20 tables, ứng với 20 class Entity trong chương trình và mỗi một Entity như vậy phải implement ít nhất 4 function GetById, Save, Delete, Update thì đúng là một cực hình. Người ta sẽ giải quyết vấn đề trùng lặp code này bằng cách sử dụng Generic của .NET framework 2.0, tất cả những hàm tương tự nhau trong các lớp Entity sẽ được implement trong một class Generic:

Code 5: Lớp Generic Dao AbstractNHibernateDao

- Các lớp Dao trong chương trình ngoài implement interface IxxxDao sẽ inherit từ lớp abstract này:

Code 6: Cách sử dụng lớp Generic AbstractNHibernateDao

IV.2 DAO Factory


- Dao Factory là gì và tại sao ta lại cần một lớp Factory? Nếu bạn có quan tâm đến Design Pattern chắc là bạn đã nghe đến các pattern như Abstract Factory, Factory Method. Một Dao Factory có khả năng tạo ra các Dao Object theo một tiêu chuẩn nào đó. Trong project này ta sử dụng NHibernate để thao tác với cơ sở dữ liệu, vậy thì Dao Factory của ta sẽ tạo ra các NHibernate Dao Object để tương tác với NHibernate. Ta sẽ tạo ra một lớp là NHibernateDaoFactory với tiêu chí đó, code như sau:

Code 7: Lớp NHibernateDaoFactory
Các bạn đang xem bài viết ASPNET bằng MVP và NHibernate phần 2 từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)

IV.3 Nhận kết quả từ Stored Procedured như thế nào?


- Trong bài trước chúng ta có implement lớp HistoricalOrderSumary để giữ giá trị trả về khi gọi một stored procedure. Ta cũng đã làm một file mapping xml tương ứng cho nó. Nhưng để thực hiện các bước cụ thể nhằm map các kết quả từ database, ta phải làm quen một khái niệm trong NHibernate là IQuery và Transform. Chúng ta sẽ tạo một instance kiểu IQuery bằng static method GetNamedQuery với parameter là tên query được khai báo trong file mapping XML, từ đó hướng dẫn NHibernate map kết quả trả về bằng constructor của lớp HistoricalOrderSumary:

Code 8: Cách map kết quả từ stored procedure

V. Kết luận


- Qua phần hai này, chúng ta đã làm quen với khái niệm Session của NHibernate một cách rất khái quát :bbpcuoi5:, trong bài kế tiếp chúng ta sẽ tìm hiều kĩ hơn tại sao tác giả lại tổ chức lớp lang như vậy. Các lớp phục vụ Session management này được viết một lần và sử dụng lại ở nhiều project khác nhau nên tác giả đề nghị ta đưa các lớp này qua một assembly khác để tái sử dụng. Interface IDao và lớp generic Dao AbstractNHibernateDao cũng được đặt trong assembly này vì chúng rất generic và có thể tái sử dụng ở những project khác mà không cần sửa code. Trong assembly EnterpriseSample.Data, chúng ta implement các lớp Dao cũng như NHibernateDaoFactory để tạo ra các NHibernate Dao. Nhờ sử dụng Generic nên rất nhiều code được sử dụng lại nên các bạn thấy rằng rất ít lớp trong EnterpriseSamle.Data và code của những lớp này cũng khá là gọn. Các lớp Dao cũng như lớp Generic Dao sử dụng một instance NHibernate.ISession để thao tác với database, ta chỉ cần truyền đường dẫn file config và Session Manager sẽ trả về một NHibernate session. Mục đích của cách làm này là giúp giảm thiểu số lần mở connection đến database server để tăng performance. Chúng ta sẽ còn bàn nhiều về các kĩ thuật liên quan trong bài thứ 4.:bbpraroi:

Code phần 2: http://nthoaiblog.googlepages.com/EnterpriseSample-part2.zip
Các đoạn code minh họa trong bài viết được rút gọn cho dễ hiểu, code được implement cuối cùng trong project sẽ có nhiều điểm khác biệt với hình...
(Còn tiếp)

Viết ASP.NET bằng MVP và NHibernate phần 1 - Domain Classes

Bài viết này được dịch, tóm tắt và bổ sung dựa theo bài viết NHibernate Best Practices with ASP.NET, 1.2nd Ed trên code Project. Các code sample trong bài dựa vào database Northwind của Microsoft và tham khảo 100% từ code mẫu của tác giả Billy McCafferty.

I/ Introduction - Why use an ORM?


Hiện nay vẫn có nhiều người không chấp nhận các công nghệ ORM, nói chung đó thường là những người thích dùng đồ chơi của Microsoft. Giang hồ thường có một luật bất thành văn rằng “nếu nó chưa được Microsoft nghĩ ra thì nên đợi xem Microsost đưa ra cách làm trước”. Cho nên sau một thời gian được chờ đợi hơi lâu, Microsoft cũng nghĩ ra LinQ to Entities trong C# 3.0. Vậy là thời điểm hiện nay, lập trình viên được lựa chọn nhiều công nghệ ORM để sử dụng. Trong số những người không thích ORM, có những người cho rằng ORMs sẽ làm giảm performance của chương trình và chúng chỉ giúp rút ngắn các phase đầu trong quá trình phát triển chương trình (làm Data Acess Layer). Một số ý kiến còn cho rằng sử dụng ORM còn có thể gây khó khăn trong việc maintain project. Và chỉ đến khi các vấn đề trên được chú ý thì người ta mới nhìn nhận những sự thật sau:

+ Dưới sự hỗ trợ của ORMs điển hình như NHibernate sẽ làm tăng performance của bạn với cương vị là một lập trình viên. Càng tốn nhiều thời gian để làm data access layer thì thời gian còn lại của bạn để làm những phần khác cũng như tối ưu hóa chương trình (nếu cần thiết) sẽ giảm đi. Khi ta dùng một số profiling tool để phát hiện nguyên nhân chạy chậm thì sẽ phát hiện một số ít nơi trong code là thủ phạm, khi đó chúng ta buộc lòng phải thực hiện các sửa đổi cần thiết để giải quyết. Trong những trường hợp như vậy thì có hay không có sử dụng ORM đều như nhau. Mặc dù rất hiếm gặp nhưng nếu thủ phạm làm cho chương trình chạy chậm là ORM framework và không có cách nào để sửa code thì ta vẫn còn một chiêu cuối là thay luôn một ORM framework khác, thậm chí implement lại data access layer theo cách của mình nếu như bạn muốn, tất nhiên chỉ làm được như vậy khi data access layer của chúng ta được tổ chức khéo léo bằng cách sử dụng nhiều abstraction.

+ Điểm thứ hai đối với các ORM, đặc biệt là NHibernate framework, đã hỗ trợ tất cả các yêu cầu đối với một framework truy xuất cơ sở dữ liệu. Nó giúp tiết kiệm công sức của lập trình viên, giúp cho chương trình chạy ổn định và dễ maintain. Tất nhiên caching cũng được hỗ trợ trong NHibernate. Ngoài ra, các feature khác như lazy-loading, inheritance, generics, hỗ trợ stored procedure cũng có trong NHibernate.

+ Điểm cuối cùng, những người luôn cho rằng ORMs như NHibernate sẽ khó maintain về sau chính là những người đang làm việc với những hệ thống phần mềm đã không được thiết kế tốt để có khả năng maintain bất cứ một data access layer nào. Vì vậy, dù Nhibertate là một lựa chọn tốt cho những chương trình sử dụng database thì không có nghĩa là bạn không cần thiết kế chương trình thật tốt.

- Không cần phải nói nhiều nữa, cũng như các ORM tools khác, NHibernate sẽ giảm bớt hàng ngàn dòng code cũng như các stored procedures cho chúng ta, vì thế nó cho phép chúng ta dành thời gian và công sức cho phần chính của chương trình: domain model và bussiness logic.

II/ Defining the Domain Layer



- Khi làm việc với project .NET có sử dụng database, có lẽ việc đầu tiên mà nhiều người sẽ làm là viết các lớp Domain. Lớp Domain hay còn gọi là Entity là lớp C# tương ứng với một table trong database. Các property trong những lớp Entities cũng tương ứng với các column trong table đó, và tất nhiên kiểu dữ liệu của chúng cũng tương ứng 1:1 với nhau. NHibernate sẽ sử dụng những lớp Entities được định nghĩa trong project kết hợp với các file XML mà chúng ta sẽ tạo ra và dựa vào đó để truy xuất database. Các file XML này thường được đặt tên ứng với tên lớp của chúng ta, ví dụ ta có lớp Customer.cs thì người ta thường tạo ra một file tương ứng là Customer.hbm.xml. HBM là chữ viết tắt của Hibernate mapping. Nội dung xml của nó sẽ dùng để mapping các field của lớp với các column trong database và tất nhiên chúng ta phải viết theo đúng quy cách của NHibernate. Quy tắc đặt tên file như tôi nói ở đây chỉ nhằm mục đích dễ quản lý, thực ra chúng ta vẫn có thể viết tất cả các đoạn XML mapping trong cùng 1 file và đặt tên file xml này tuỳ ý vì NHibernate sẽ được chỉ định nơi để tìm các nội dung XML này trong file app/web config. Các file XML này sẽ được build cùng với projects như là các embeded resources nên chúng ta phải chú ý đến chi tiết này khi thêm các file HBM vào project.

- Như đã nói thì các lớp Entity sẽ tương ứng với các table trong database. Về lý thuyết chúng ta có thể đặt tên các property tuỳ ý không cần phải theo tên của column trong database, NHibernate sẽ phân biệt được và biết cách map giữa property nào với column nào trong database. Nhưng để dễ hiểu và dễ bảo trì, người ta thường tự giác đặt tên chúng như nhau. Sau đây là ví dụ về một lớp Entity và nội dung XML mapping tương ứng với nó:

ASP.NET, NHibernate và MVP

ASP.NET, NHibernate và MVP
Code 1: 1 Domain class và xml file mapping tương ứng

- Như vậy Domain layer của chúng ta sẽ có các lớp Entity, các file HBM tương ứng. Chúng ta sẽ đặt tên cho project chứa các file này là XXXX.Core. Ngoài ra, project này còn chứa các interface của các data access object. Và vì chúng ta là những người có tổ chức nên các interface này sẽ được đặt trong một thư mục riêng: DataInterrfaces và vì vậy namespace của các Interface trong thư mục này sẽ tương ứng với tên thư mục của nó: EnterpriceSample.Core.DataInterfaces. Chúng ta sẽ bàn chi tiết các bước tạo những file interface này ở phần hai; còn tại sao phải đặt chúng ở Domain Layer sẽ được giải thích ngay sau đây.

II.1 Separated Interface, Implemented


- Trong project EnterpriceSample.Core không chứa bất cứ phần code nào implement các data access object, chúng ta sẽ chỉ định nghĩa các interface của chúng. Các lớp DAO implement các interface này sẽ được đặt ở một layer khác, đó sẽ là project EnterpriceSample.Data. Cách làm này có vẽ khá lạ lùng, vì chúng ta vẫn quen với suy nghĩ layer bên trên sẽ phụ thuộc vào layer bên dưới, tầng web sẽ reference đến tầng Service, Service sẽ phụ thuộc vào Data Access Layer. Thế nhưng ở đây ta sẽ làm khác đi một chút, tầng Service hay ở đây gọi là Core sẽ không phụ thuộc vào Data, ngược lại nó sẽ khai báo một số Interface nó cần sử dụng và các lớp DAO trong tầng Data sẽ phải phụ thuộc vào layer Core này và implement các Interfaces được khai báo trong đó. Kĩ thuật này được gọi là “Separated Interface”. Nếu như chúng ta gọi EnterpriceSample.Core là một “uppler-level layer” và EnterpriceSample.Data là “lower-level layer” thì: "each of the upper-level layers declares an abstract interface for the services that it needs. The lower-level layers are then realized from these abstract interfaces. ... Thus, the upper layers do not depend on the lower layers. Instead, the lower layers depend on abstract service interfaces declared in the upper layers" (Robert Martin).
ASP.NET, NHibernate và MVP
Hình 1: Quan hệ giữa project Core và project Data
Các bạn đang xem bài viết ASPNET bằng MVP và NHibernate từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)

- Chúng ta sẽ bàn về các bước tạo các Interface này ở bài sau.

II.2 Generic IDs and Object Comparisons


- Trong project EnterpriceSample.Core, phần lớn các lớp Domain/Entity inherits từ lớp DomainObject. Lớp này khai báo một số function để phục vụ trong việc so sánh hai đối tượng cùng kiểu. Domain Object là một lớp Generic, nó nhận vào một kiểu dữ liệu dùng để khai báo ID của một domain object. Generic property kiểu này cho phép ta định nghĩa chung các lớp có property ID kiểu string như lớp Customer và cả các lớp có property ID kiểu long; ví dụ như Order. Các bạn cũng chú ý rằng ID này chỉ khai báo public getter nhưng không cho public setter. Giả sử ta lấy một Customer từ database và vô tình thay đổi giá trị ID của nó rồi save lại, dữ liệu này sẽ ghi đè vào record của Customer khác trong database. Vì vậy các lớp Entity chỉ nên được khai báo ID với setter là protected, có nghĩa là chỉ NHibernate sẽ đọc database vả chỉ nó được set các thông tin đó vào Domain object của chúng ta. Nhưng sẽ có một số trường hợp các domain object cần được set giá trị cho ID của chúng. Với những trường hợp như vậy, ta có thể sử dụng một cách work-around là viết một function để Set ID như ý muốn. Mỗi lần sử dụng hàm này buộc chúng ta phải nghĩ đến những trường hợp save đè dữ liệu không lường trước. Trong bài viết, tác giả có giới thiệu thêm một interface là IHasAssignedId để giúp chúng ta làm việc này. Đoạn code sau đây sẽ mô tả cách sử dụng interface này:
ASP.NET, NHibernate và MVP
Code 2: Cách sử dụng interface IHasAssignedId

- Trong code các bạn thấy lớp Customer có khai báo implement interface IHasAsssigneed và bên trong thân code của nó sẽ phải implement method SetAssignedIDTo. Ngoài ra thân hàm có sử dụng các public static method của lớp Check. Đây là một lớp Util chúng ta sử dụng để kiểm tra các ràng buộc và sẽ được đề cập trong bài viết khác.

II.3 Mapping the Domain to the Database


- Trong NHibernate có hai cách để map các domain objects vào database.: HBMs sử dụng các file XML và sử dụng các Attribute. Thuận lợi chính khi sử dụng XML mapping là chúng hoàn toàn độc lập với những lớp C# mà chúng mô tả. Điều này giúp cho lớp Domain của chúng ta giữ được tính chất như một POCOs (plain old C# objects). Nhưng bù lại các file mapping xml lại sinh ra một bất lợi đó là chúng ta phải tốn khá nhiều công sức để giữ cho những nội dung mapping phải luôn chính xác giữa định nghĩa lớp và cấu trúc database:

ASP.NET, NHibernate và MVP
Code 3: Quan hệ giữa các Entity trong code C#

ASP.NET, NHibernate và MVP
Code 4: Quan hệ giữa các Entity được khai báo đúng quy cách trong XML mapping file

- Các bạn thấy rằng hai lớp Product và Suplier có quan hệ với nhau, và trong xml mapping data tương ứng cũng có quan hệ one-to-many để thể hiện ràng buộc này. Nếu như chúng ta có thay đổi database, thì việc tiếp theo cần làm ngay là sửa file mapping này theo thay đổi đó.

- Ngược lại sử dụng Attribute để map lại buộc chúng ta can thiệp trực tiếp vô code, nhưng được cái cách đó giúp cho việc mapping đơn giản hơn. Sử dụng Attribute để map làm cho lớp Domains của chúng ta trở nên giống như khi sử dụng Active Record. Ngoài việc can thiệp trực tiếp vào code của các lớp Domain, cách làm này còn buộc chúng ta phải reference tới NHibernate.Mapping.Attributes và khiến cho Domain Layer của chúng ta mất đi tính độc lập với các assembly đáng lẽ chỉ xuất hiện trong tầng Data. Dường như có một convention chung là domain layer nên độc lập với những gì của data layer. Khi làm đúng như vậy, bạn sẽ không phải bận tâm khi cần có sự thay đổi ở data layer. Do đó, bất cứ khi nào tạo một project mới, ta nên quyết định xem nên sử dụng ORM nào; sử dụng kết hợp nhiều thứ một lúc có thể gây ra confusion khi ta không biết object nào cần nên map và cái nào không cần thiết để map. Dù sao đi nữa thì đó là kinh nghiệm của tác giả rất đáng để tham khảo; còn đoạn code dưới đây sẽ cho thấy cách sử dụng NHibernate mapping bằng Attributes:
ASP.NET, NHibernate và MVP
Code 5: Sử dụng Attribute để map
Các bạn đang xem bài viết ASPNET bằng MVP và NHibernate từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)

(Nếu muốn sử dụng Attribute để mapping, các bạn phải download thêm 1 assembly là NHibernate.Mapping.Attributes trên sourceforce mới sử dụng được.)

II.4 NHibernate Support for Stored Procedures


- Nếu bạn muốn map kết quả trả về của một Stored Procedure về thì làm thế nào? Cũng như các ORM tool khác, NHibernate cũng hỗ trợ chuyện đó dễ dàng. Giả sử như bạn cần trả về số lượng các product được order bởi một customer biết trước. Chúng ta sẽ viết một stored procedure để thực hiện chuyện đó. Ngoài ra chúng ta sẽ viết một value object HistoricalOrderSummary để lưu trữ kết quả trả về của Stored Procedure. Chúng ta lưu ý là lớp này không inherite từ DomainObject và vì vậy không cần một HBM đúng nghĩa tương ứng với nó. Tuy nhiên chúng ta sẽ tạo ra một file HistoricalOrderSummary.hbm.xml và khai báo trong đó tên của Stored Procedure sẽ sử dụng và kết quả đó map như thế nào. (Chi tiết làm sao các dữ liệu được map với domain objects sẽ được đề cập trong bài viết sau khi chúng ta bàn về các bước implement project EnterpriceSample.Data)

ASP.NET, NHibernate và MVP
Code 6: Một value class để giữ kết quả trả về từ stored procedure

ASP.NET, NHibernate và MVP
Code 7: Map kết quả trả về từ stored procedure trong XML

- Đưa ra khả năng tương tác với stored procedure lại nảy sinh một vấn đề giữa quyết định cái gì nên được xử lý tính toán bằng stored procedure và cái gì nên được thực hiện trong domain layer. Ngoài stored procedure, C# code có thể lấy tất cả các order được customer đó đặt mua, duyệt qua các order đó và tính tổng các product. Nhưng rõ ràng rằng để SQL server thực hiện chuyện đó sẽ hiệu quả hơn nhiều nếu như customer này đã đặt hàng ngàn Order dẫn đến rất nhiều thông tin được lưu trong database. Mặt khác, giả sử khi ta thử dùng một chương trình analysis nào đó để để đo thử và thấy rằng tính toán bằng C# sẽ chạy lẹ hơn khi gọi Stored Procedure, thì lúc đó nên để domain layer làm công việc xử lý nếu muốn. Nói chung chúng ta nên dung hòa giữa hai lựa chọn này để tối ưu hóa performance.

III. Tóm tắt


Trong phần một này chúng ta đã tạm thời chấp nhận việc phải tạo các lớp Domain để sử dụng với NHibernate, chúng ta cũng biết rằng ngoài việc tạo các lớp này còn phải viết thêm các file XML để mapping chúng với database. Tất cả các file này nên được đặt trong cùng một project, có thể được đặt tên project là TênProject.Core. Thêm nữa, các file XML phải là các embeded resource và phải được viết theo convention của NHibernate. Trong phần tiếp theo chúng ta sẽ tìm hiểu kĩ hơn bước khai báo các Interface được sử dụng trong layer Data.

Code phần 1: http://nthoaiblog.googlepages.com/EnterpriseSample-part1.zip
Các đoạn code minh họa trong bài viết được rút gọn cho dễ hiểu, code được implement cuối cùng trong project sẽ có một số điểm khác biệt chẳng hạn như thêm constructor, thêm một số method, ...

(Còn tiếp)

MVP - Model View Presenter

Giới thiệu

- Trong bài viết giới thiệu về mô hình MVC, chúng ta đã đề cập đến các nhược điểm của ngôn ngữ ASP. Có thể thấy hiện giờ ít trang web nào sử dụng công nghệ ASP cũ để phát triển nữa mà thay vào đó là các ngôn ngữ mới hơn, tiện ích hơn như PHP, ASP.NET, Java, v.v. Microsoft giới thiệu ngôn ngữ ASP.NET với điểm nhấn tách riêng các thành phần giao diện và xử lý dữ liệu bussiness logic bằng mô hình code-behind. ASP.NET có vẽ như là một lựa chọn tốt cho các ứng dụng web đơn giản, nhưng chính mô hình code-behind sẽ gây ra một số khó khăn cho các ứng dụng web vừa và lớn:

- Code-behind dường như được thiết kế để làm quá nhiều việc, nó vừa là một nơi để xử lý các event, kiểm soát các workflow trong chương trình và nó còn là một layer trung gian giữa dữ liệu bên dưới và giao diện phía trên. Người lập trình có thể viết mọi thứ trong code-behind và làm cho nó trở nên vô cùng rối rắm. Để cho code-behind làm quá nhiều thứ sẽ làm cho code của bạn khó kiểm soát và khó test (Unit Test). Trong những ứng dụng web lớn, người ta xem một design tốt khi nó giảm thiểu sự phụ thuộc lẫn nhau giữa các tầng (layer) và làm sao để cho code-behind càng đơn giản càng tốt. Với mô hình Model-View-Presenter, chúng ta sẽ thấy rằng code-behind được giữ cho vô cùng đơn giản và không hề dính dáng gì đến những xử lý trên giao diện Web.

- Một nhược điểm khác của code-behind là nó hầu như rất khó để sử dụng lại những xử lý giao diện (presentation logic) giữa những trang code-behind khác nhau nếu không đưa các xử lý đó ra các lớp Utility hay Helpers để tránh trùng lặp code. Tất nhiên để làm được điều đó cần một thời gian để tổ chức các lớp cho thich hợp. Tuy nhiên, nó thường dẫn chúng ta đến việc thiết kế các lớp một cách rời rạc và giống như khi sử dụng ASP. Trong một design tốt, mỗi class trong hệ thống nên có duy nhất một nhiệm vụ và mục đích rõ ràng, nếu chúng ta cần tạo ra một class chỉ để tránh trùng lặp code giữa 2 hoặc nhiều nơi khác thì đó là một dấu hiệu xấu, một thiết kế tồi.

- Và một nhược điểm cố hữu mà tôi thường nhắc đến là khả năng sử dụng Unit Test để test các lớp code-behind này. Các lớp code-behind đều kế thừa từ System.Web.UI.Page nên khó có thể viết các Unit Test một cách tự nhiên và thuận lợi được.

- Đã có nhiều kĩ thuật được tìm ra để giải quyết vấn đề của code-behind. Chẳng hạn như Castle Monorail project đã kế thừa những lợi điểm của Ruby-On-Rails và không sử dụng mô hình ASP.NET event. Thực ra cũng giống như ASP.NET MVC Framework, Castle Monorail hướng người lập trình đến cách phát triển ứng dụng Web theo MVC hơn là MVP, và khi áp dụng những framework này, chúng ta sẽ làm web ASP.NET theo một cách hoàn toàn khác, không có postback, không có server controls,... Khác với các framework hỗ trợ MVC, các framework hỗ trợ MVP là những giải pháp trong đó vẫn giữ mô hình event của ASP.NET nhưng hướng đến mục tiêu làm cho code-behind càng đơn giản càng tốt. Model-View Presenter sẽ giúp bạn làm được chuyện đó mà không cần dựa trên một framework nào cả. Đó cũng chính là lý do rất nhiều lập trình viên Web đã chọn MVP thay vì MVC trước khi có những framework hỗ trợ MVC như hiện nay.

- Bên cạnh các ASP.NET MVC Framework, Castle Monorail, hiện nay đã có những framework hỗ trợ MVP cho .NET như MVC# Framework và NMVP Framework. Hi vọng tôi có thời gian tìm hiểu và chia sẽ với các bạn muốn quan tâm. Trong bài viết này, tôi sẽ trình bay khái quát lý thuyết suông về MVP; một sample code sẽ được giới thiệu trong bài tiếp theo.

Các bạn đang xem bài viết về Model View Presenter từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)
Model-View-Presenter

- Nhiều người cho rằng MVP là một design biến đổi của MVC. Điểm khác nhau dễ thấy nhất là Presenter và Controller :bbpraroi: hehe đùa thôi... Trong mô hình MVP, các lớp View sẽ được sử dụng thông qua một Interface được định nghĩa trong .NET. Các lớp Presenter tương ứng sẽ sử dụng Interface này để đọc và ghi dữ liệu lên trên các View. Trong đa số các cách implement, một View sẽ có một Presenter tương ứng của nó. View sẽ khởi tạo Presenter cho nó và truyền cho Presenter này tham chiếu đến chính nó. Khi một event nào trên view được kích hoạt chẳng hạn như button_clicked, text_changed,.. bản thân lớp View sẽ không làm gì cả mà sẽ để cho lớp Presenter xử lý những sự kiện đó. Presenter sẽ đọc dữ liệu từ View (vì nó giữ một instance của View như là một member trong class) thông qua View Interface, thực hiện những xử lý ứng với Event được kích hoạt và set những thay đổi từ dữ liệu Model lên trên View thông qua View Interface.

- Trong môi trường .NET, cùng một Presenter có thể được sử dụng cho View trên web như các trang ASP.NET hoặc được sử dụng cho các Form trong Windows Form Application. Các Presenter đọc và ghi dữ liệu thông qua một Interface của .NET nên nó hoàn toàn độc lập với layer View. Chính nhờ cách làm này mà ta có thể áp dụng Unit Test cho các lớp xử lý Presenter rất dễ dàng và nó cũng chính là một trong những lợi ích lớn nhất của MVP cho khả năng tái sử dụng.



Model View Presenter Solution Structure


Cập nhật View

- Khi thành phần dữ liệu Model được cập nhật, View cũng sẽ phải được cập nhật để hiển thị những thay đổi. Quá trình cập nhật View có thể được thực hiện bằng nhiều cách khác nhau. Có lẽ cũng từ những cách này mà Model-View-Presenter được chia thành hai loại là Passive View và Supervising Controller.

- Trong mô hình Passive View, Presenter sẽ cập nhật view tương ứng bằng cách đọc dữ liệu trực tiếp từ Model rồi set lên View, do đó các lớp View sẽ không biết gì về thay đổi dữ liệu bên dưới. Hay nói cách khác chúng hoàn toàn bị động theo như trên gọi.

- Còn trong mô hình Supervising Controller, các lớp View sẽ tương tác trực tiếp với Model bên dưới để đọc dữ liệu mà không cần thông qua các lớp Presenter. Presenter sẽ cập nhật Model; nó chỉ thay đổi những control trên View trong trường hợp có những xử lý giao diện phức tạp mà không thể khai báo trước chẳng hạn như ẩn hiện control, đổi màu text,…

Passive View and Supervising Controller


- Quyết định dùng Passive View hay Supervising Controller thường dựa trên nhu cầu test tự động của ứng dụng. Nếu bạn muốn làm một ứng dụng có thể test tự động dể dàng và cover hầu hết các xử lý bên trong thì Passive View là một lựa chọn thích hợp, mọi UI logic đều được test bằng cách viết lớp test cho các Presenter. Mặt khác, nếu bạn thích code đơn giản hơn thì Supervising Controller có vẻ là một lựa chọn thích hợp hơn. Đối với những thay đổi đơn giản trên giao diện, bạn cũng không cần đưa những xử lý cho các thay đổi đó vào lớp Presenter. Khi chọn lựa giữa Passive View và Supervising Controller, bạn nên cân nhắc các điều sau đây:

  • Cả hai cách đều cho phép bạn tăng khả năng test tự động cho các xử lý giao diện (presentation logic)
  • Passive View thường cung cấp khả năng test cao hơn Supervising Controller bởi vì tất cả các xử lý giao diện được đặt trong các lớp Presenter.
  • Supervising Controller sẽ đỡ phải code nhiều như Passive View bởi vì Passive View không cần thực hiện những cập nhật đơn giản cho View.

Tương tác với dữ liệu

- Bạn cần phân biệt rõ Model với Domain, hai khái niệm này hoàn toàn khác nhau. Chúng ta có thể implement các lớp tương tác với dữ liệu bằng nhiều cách khác nhau. Trong một số trường hợp, có thể bạn sẽ phải sử dụng Observer Pattern cho các Presenter và Model. Nó sẽ giúp cho Presenter nhận những events từ Model khi có sự thay đổi và từ đó cập nhật thay đổi đó lên trên View. Thông thường, người ta sẽ làm ra các lớp Service/Manager, các lớp này sẽ đóng vai trò như các lớp Bussiness Logic trong layer Model để giúp Presenter tương tác với database bên dưới:

MVP Tiers


Các nhược điểm của MVP

  • Có nhiều lớp, nhiều layer phải implement; có nghĩa độ phức tạp của ứng dụng sẽ tăng.
  • Bạn phải nghĩ ra một cách để kết hợp giữa các Views và các Presenters. Trong ASP.NET thì các lớp Code-Behind sẽ implement một interface IView nào đó và sử dụng một member Presenter.
  • Mô hình Model không biết gì về Presenter. Vì thế nếu dữ liệu Model bị thay đổi từ một chương trình khác thay vì bởi Presenter thì phải nghĩ ra một cách để Presenter biết. Các bạn có thể implement chức năng này nếu cần bằng cách sử dụng các events, hoặc tham khảo thiết kế Observer Pattern.



Trong bài sau tôi sẽ trình bày một sample cho Supervisinng Controller.:bbpxtay:

Dependency Injection with Spring.Net p5

(Bài viết dịch từ bài Dependency Injection with Spring.Net của David Consdorf, thêm hành thêm tiêu rồi nấu lại bởi Nguyễn Thoại :bbpraroi:)



- Như ở giới thiệu phần trước, trong phần này chúng ta sẽ xem một ví dụ khác về cách áp dụng phổ biến của dependency injection khi chương trình được tách thành nhiều tầng và các tầng này được kết nối với nhau bằng Spring.NET

Quick N-Tier Example


- Bất cứ ai từng làm việc với những ứng dụng khá lớn có thể đã biết đến kiến trúc N-tier. Ý tưởng cơ bản là ứng dụng được chia thành nhiều tầng chức năng. Mỗi tầng đảm nhiệm một chức năng nào đó và chỉ liên lạc với những tầng ở phía trên hoặc dưới nó. Ví dụ dễ gặp nhất là kiến trúc 3 tầng (lúc đi học được dạy là mô hình 3 lớp) như sau:
3tiers structures
Hình 12: Mô hình 3 lớp


- Trong ví dụ này, tầng trên cùng là tầng giao diện (presentation/view) làm nhiệm vụ hiển thị dữ liệu trên trang web, trên một ứng dụng client, một ứng dụng di động, … Kế tiếp, ta có một tầng dịch vụ (Business Logic Layer – BLL hay Service layer) làm nhiệm vụ xử lý tính toán những logic của ứng dụng. Và cuối cùng là tầng tầng dữ liệu (DAL - Data Access Layer) chịu trách nhiệm giao tiếp với cơ sở dữ liệu (database), XML hoặc bất kì loại datasource nào mà chương trình có thể sử dụng. Mỗi tầng chỉ giao tiếp duy nhất với tầng khác thông qua một interface, mỗi tầng sẽ không biết các tầng khác hoạt động như thế nào và được lập trình như thế nào.

- Ví dụ tầng giao diện sẽ gọi tầng dịch vụ để tạo ra một business object để giữ những gì người dùng nhập vô, sau đó tầng dịch vụ sẽ tạo ra một đối tượng dựa trên những gì nhập vào, tính toán gì đó nếu cần thiết và tiếp tục gọi tầng dữ liệu để lưu trữ những thông tin này. Tầng dịch vụ không cần quan tâm tầng giao diện đang hiển thị dữ liệu trên trang web hay trên một windows application, nói cách khác nó không quan tâm ứng dụng thuộc loại nào, và nó cũng chẳng cần quan tâm làm thế nào tầng tầng dữ liệu lưu thông tin xuống dưới, nó cũng không quan tâm ứng dụng đang sử dụng loại cơ sở dữ liệu nào. Những cách làm này giúp chúng ta dễ dàng thay đổi chương trình để thích hợp cho nhiều loại người dùng khác nhau, trên desktop, trên web hoặc trên thiết bị di động đều được. Thêm vào đó khi có cần thay đổi loại cơ sở dữ liệu, chúng ta chỉ cần viết lại hoặc thay thế (nếu có sẵn) tầng dữ liệu. Nếu có một tầng nào đó cần thay đổi thì chỉ có tầng đó phải sửa đổi và những tầng khác có thể được sử dụng lại

- Spring.Net và Dependency Injection giúp chúng ta xây dựng những ứng dụng nhiều tầng bằng cách chia ứng dụng ra thành nhiều component. Với Spring.Net, chúng ta có thể giữ cho những components trong 1 tầng độc lập với những tầng khác và có thể inject dependency vào những tầng khác dễ dàng nhờ vào file cấu hình xml.
Các bạn hãy tham khảo đoạn code ví dụ cách liên kết một đối tượng từ tầng dịch vụ với một đối tượng từ tầng tầng dữ liệu:



public interface IServiceExample {

void doOperationOnObject(Object obj);
Object getObject(int objectID);
void saveObject(Object obj);
}

public class ServiceExample : IServiceExample {

public IDAOExample _daoExample;

public ServiceExample(IDAOExample daoExample) {
_daoExample = daoExample;
}

public void doOperationOnObject(Object obj) {
// Enter business logic for operation here
}

public Object getObject(int objectID) {
// Use DAO to retrieve object from the database
return _daoExample.getObject(objectID);
}

public void saveObject(Object obj) {
// Use DAO to save object to the database
_daoExample.saveObject(obj);
}
}

public interface IDAOExample {
Object getObject(int objectID);
void saveObject(Object obj);
}

public class DAOExample : IDAOExample {

public DAOExample() {
}

public Object getObject(int objectID) {
// Put database code to retrieve object here
}

public void saveObject(Object obj) {
// Put database code to save object here
}
}

Hình 13: Services & DAO objects


<object name="ServiceExample" type="ServiceExample, __Code" singleton="true">
<constructor-arg name="daoExample" ref="DAOExample" />
</object>

<object name="DAOExample" type="DAOExample, __Code" singleton="true">
</object>

Hình 14: Services & DAO mappings

- Trong ví dụ trên, bạn có một đối tượng thuộc tầng dịch vụ giao tiếp với một đối tượng thuộc tầng dữ liệu để lưu thông tin của một object xuống database. Đối tượng DAO được nhúng vào bên trong đối tượng tầng dịch vụ nhờ Spring.NET. Bằng cách này, sẽ không có sự liên kết cụ thể nào trong code giữa các tầng (layers).

- Nếu một ngày đẹp trời nào đó khách hàng yêu cầu bạn đổi từ MySQL sang sử dụng Oracle, tất cả những gì bạn cần làm là tạo mới những lớp DAO cho Oracle, sau đó sửa file config của Spring.NET sang Oracle DAO object. Không có dòng nào trong code của tầng dịch vụ (BLL) cần phải thay đổi hay build lại.

- Bạn cũng nên chú ý là trong trường hợp này, tầng giao diện (Presentation) khi sử dụng những đối tượng dịch vụ cũng có thể nhờ Spring.NET để lấy; cũng tương tự như những đối tượng ở tầng dịch vụ sử dụng Spring.NET để gọi các đối tượng ở tầng dữ liệu. Một điều cần chú ý nữa là các đối tượng ở tầng dịch vụ và tầng dữ liệu đều là singletons bởi vì chúng được sử dụng để tính toán và lưu trữ trong chương trình, chúng chẳng cần giữ lại dữ liệu gì cả. Hiện nay, có rất nhiều framework .NET hỗ trợ các bạn sẵn tầng DAL với các chức năng có sẵn như :bbpxtay: lazy load, caching, optimized queries,… Các bạn chỉ cần viết một số store procedure hoặc config một chút xíu là có thể sử dụng được mà không cần viết code nhiều. Các framework đó cũng rất linh hoạt khi cần phải chuyển đổi loại database. Hiện có mốt số framework rất nổi tiếng như Ibatis, NHibernate, Active Record, LinQ,… tùy vào nhu cầu và thói quen bạn có thể lựa chọn framework thích hợp nhất cho mình. Nếu người là người cổ điển có thể NHibernate thích hợp với bạn, còn nếu là dân sành điệu thích thời trang thì LinQ là lựa chọn tốt :bbpsdieu2:.Các bạn có thể tìm hiểu thêm thông tin về các Object-relational mapping (O/R Mapping) framework từ đây, theo tôi đó là một trong những bài viết rõ ràng súc tích và dễ hiểu nhất.


Phần 1

Phần 2

Phần 3

Phần 4

Phần 6

(Các bạn đang xem phần 5)

rss
 

About Me

Place I've live
Near Bossley Park, Sydney, NSW, Australia
Place I've work
  • Freelancer (from 06/2010 to present)
  • Harvey Nash (from 05/2008 to 06/2010)
  • DataDesign Vietnam (10/2005 to 04/2008)
Place I've studied
  • University of Natural Science (Bachelor of Science HoChiMinh City Vietnam From 2001 to 2005)
  • Le Hong Phong High School (HoChiMinh City Vietnam From 1997 to 2000)