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)

Posted in Labels: , , , , , |

5 comments:

  1. Quỳnh Orange Says:

    Trên thực tế, nếu một hệ thống mở rộng liên tục và nghiệp vụ ở nhiều lĩnh vực thì việc đưa các Entity class vào chung một project là điều không thể vì sẽ tốn công đi merge code sau này, và khi đó công việc mệt nhất khi với NHibernate là cái mapping, nhưng với Castle ActiveRecord thì đỡ mệt hơn nhiều rồi (tất nhiên là cần chấp nhận chuyện đặt Attribute để thay cho file mapping).

  2. Anonymous Says:

    Neu su dung .Net FW 2.0 thi hay thu SubSonic. SubSonic cung la ORM tool, tac gia cua no' da duoc Microsoft thue^ ve` de phat trien tiep SubSonic nhu mot ORM di kem voi ASP.NET MVC.
    Theo danh gia' cua tao thi SubSonic de su dung hon NHibernate rat nhieu, ko co' Mapping gi het.

    Ah, quen no'i, tao la` Hoang Hai o TH2001, hi vong con nho tao :))

  3. Unknown Says:

    - Phải ko đó mài, MS ra LinQ với Entity Framework còn làm thêm cái SubSonic này nữa sao. Tau cũng nghe mấy đứa bạn nói về SubSonic nhưng chưa thử bao giờ, có dịp sẽ tìm hiểu.

    - Dễ sử dụng hay không chưa thử chưa biết, nhưng tau nghĩ xài nhiều sẽ quen. NHibernate được cộng đồng công nhận và thử nghiệm từ khá lâu nên xài nó có vẻ yên tâm hơn :D.

  4. Quỳnh Orange Says:

    Mà Hammet (kiến trúc sư của Castle ActiveRecord) được Microsft thuê về để làm cái ADO.NET Entity Framework, cũng đang test rồi đấy.

    Xem thử tại đây xem nào: http://hammett.castleproject.org/

  5. Anonymous Says:

    Thực tế bây giờ, developer VN mình giống người đẽo cày giữa đường, cái gì cũng muốn học hết.

    Cty thì tụi nó chọn gì mình phải làm nấy, trừ phi mình mở cty riêng, hoặc là mình làm chief thì mới thay đổi được.

    Cảm ơn Mr. Thoại nhé, mình xài NHibernate cũng lâu rồi, bây giờ mới tìm được đồng môn.

    Mình vẫn thích NHibernate vì nó sẽ port được toàn bộ thế mạnh của Hibernate bên Java qua, làm với Hibernate rồi thì thấy làm NHibernate còn tiện hơn nhiều vì có những thứ như FluentNHibernate hay NHibernate.Validator.

    Nhiều người thích Entity Framework của MS vì cái gì do MS nghĩ ra thì có nghĩa nó dễ làm và tiện dụng. Hướng đó cũng hay, nhưng nếu mà đẽo cày giữa đường, tức là có cái gì mới thì chạy theo cái đó mà bỏ công sức bỏ ra khi cũ thì có khi chạy cả đời chả làm được cái gì.

    Mong được làm quen với bạn. ( Mình ở Huế :))

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)