12/08/2018, 15:46

Tiếp cận dữ liệu trừu tượng trong thiết kế và DOMA (Domain Oriented Database Mapping)

Các bạn lập trình viên Java cho ứng dụng doanh nghiệp có lẽ không xa lạ gì với Hibernate, một thư viện đóng vai trò cầu nối chuyển đổi giữa các Object Java với các cơ sở dữ liệu quan hệ. Trong bài viết này, nhân tiện đang làm một dự án có sư dụng DOMA - một thư viện ánh xạ từ kiểu bảng dữ liệu sang ...

Các bạn lập trình viên Java cho ứng dụng doanh nghiệp có lẽ không xa lạ gì với Hibernate, một thư viện đóng vai trò cầu nối chuyển đổi giữa các Object Java với các cơ sở dữ liệu quan hệ. Trong bài viết này, nhân tiện đang làm một dự án có sư dụng DOMA - một thư viện ánh xạ từ kiểu bảng dữ liệu sang đối tượng, xin phép viết một bài chia sẻ cùng mọi người về thư viện này. DOMA (Domain Object Database MApping) có chức năng tương tự Hiberate cũng chuyển đổi giữa đối tượng java với các bảng dữ liệu. Để thực hiện việc chuyển đổi này, DOMA sử dụng các Entity để phản ánh các cột của bảng vào các fields của đối tượng. Ví dụ:

@Entity()
@Table(name="employee")
public class Employee{
      @Column(name="employee_id")
      @Id
      private String employeeId;
      @Column(name="employee_name")
      private String employeeName;
      
      //các setter và getter 
}

Sau khi có các entity mapping, chúng ta sẽ phải viết các câu lệnh SQL để lấy dữ liệu từ bảng và đưa vào các Entity (điều này khác với Hibernate hay JPA), hoặc cập nhật dữ liệu từ đối tượng vào bảng. Việc này được thực hiện trong các Dao Interface.

@Dao
@ConfigAutowireable
public interface EmployeeDao{
   @Select
   public Employee selectById(String employeeId);
   @Insert
   public int insert(Employee entity);
   @Update
   public int update(Employee entity);
}

Sau khi có các DAO interface, chúng ta tạo các file sql có cùng tên với method, và có các tham số giống như tham số của method. Các annotation (@Select, @Insert, @Update) sẽ tương ứng với các câu lệnh SQL (SELECT, INSERT, UPDATE). Ví dụ

file: META-INF/EmployeeDao/selectById.sql

SELECT /*%expand%/*
FROM Employee
WHERE employee_id=/*employeeId*/'1'

Vì các Dao là các Interface, nên phải có các class implement các Interface đó rồi gọi các câu lệnh SQL trong các file * . sql tương ứng. Việc này được thực hiện bằng cách cài doma Eclipse plugin (hoặc IntelliJ plugin) để sinh class impl trong quá trình chúng ta code. Các khái niệm cơ bản của DOMA:

  1. Domain Như tên của thư viện (Domain Oriented Database Mapping), dữ liệu trong DOMA được chia thành các domain (có thể gọi là kiểu dữ liệu trừu tượng hay kiểu dữ liệu logic). Domain là một khái niệm dữ liệu trừu tượng dùng trong quá trình thiết kế. Ví dụ: "số điện thoại" là một domain có thể dùng để lưu số điên thoại di động, số điện thoại cố định, số fax..... Các domain có thể thêm các thao tác riêng biệt cho từng loại, ví dụ với kiểu dữ liệu phone number thì có thể thêm "getAreaCode" để lấy mã vùng điện thoại. Có 2 loại domain là "Internal Domain" và "external Domain". "Internal Domain" là những kiểu dữ liệu định nghĩa với từ khoá @Domain:
@Domain (valueType=String.class)
public class PhoneNumber(){
     private  String phoneNumber;
     public PhoneNumber(String v){ phoneNumber = v;}
     public String getValue(){return phoneNumber;}
     public String getAreaCode(){return phoneNumber.substring(2);}
}

"External Domain" được sử dụng khi chúng ta đã có một class và không thể sửa đổi code của class đó (VD: vì nó nằm trong một thư viện khác đã đóng gói, ....). Để sử dụng một class có sẵn làm domain, chúng ta sử dụng Domain Converter.

@ ExternalDomain 
public  class  PhoneNumberConverter  implements  DomainConverter < PhoneNumber ,  String >  {

    public  String  FromDomainToValue ( PhoneNumber  domain )  { 
        return  domain . getValue (); 
    }

    Public  PhoneNumber  fromValueToDomain ( String  value )  { 
        if  ( value  ==  null )  { 
            return  null ; 
        } 
        return  new  PhoneNumber ( value ); 
    } 
}
//đăng ký Domain Converter vào hệ thống chương trình
@ DomainConverters ({  PhoneNumberConverter . Class  }) 
public  class  DomainConvertersProvider  { 
}
  1. Embeddable Class Embeddable class sử dụng để nhóm các thuộc tính (các cột) của đối tượng thành một kiểu dữ liệu thuận tiện hơn cho việc sử dụng. Ví dụ:
@ Embeddalbe 
public  class  Address  {

    final  String  city ;

    final  String  street ;

    @ Column ( name  =  "ZIP_CODE" ) 
    final  String  zip ;

    public  Address ( String  city ,  String  street ,  String  zip )  { 
        this . city  =  city ; 
        this . street  =  street ; 
        this . zip  =  zip ; 
    } 
}

Và sau đó có thể sử dụng như một kiểu dữ liệu con bên trong một entity;

@Entity 
public  class  Employee  { 
    @Id 
    Integer  id ;

    Address  Address ; 
}
  1. Entity class Các entity class dùng để ánh xạ giữa đối tượng Java với bảng dữ liệu
@Entity
public class Employee{
@Column(name="employee_id")
private String employeeId;
}

Các cột được ánh xạ bằng annotaion @Column

Như vậy là chúng ta có các thành tố cơ bản của DOMA để có thể ánh xạ giữa các class Java với các bảng dữ liệu. Còn nhiều chi tiết khác thì có thể tham khảo tại đây: https://doma.readthedocs.io/ja/stable/

  1. DOMA vs Hibernate (và các ORM khác) Nếu như chúng ta chỉ đơn thuần đọc các thông tin phía trên, thì sẽ thấy DOMA không đưa lại một cải tiến nào so với các thư viện ORM (Object Relation Mapping) khác. Vậy lý do chính của việc sử dụng DOMA là gì? Ở đây, tôi muốn liên hệ giữa DOMA và các công đoạn trong quá trình phát triển phần mềm, (hơn là việc đi sâu vào các vấn đề kỹ thuật trong việc cài đặt, viết code....) DOMA khuyến khích việc sử dụng các data domain, là một dạng dữ liệu trừu tượng rất thuận tiện trong công tác thiết kế và giao tiếp với người dùng cuối. Đối với những người này, việc sử dụng các kiểu dữ liệu vật lý CHAR, VARCHAR thậm chí là text sẽ cực kỳ khó hiểu và do đó rất khó để chúng ta tiến hành kiểm tra các phạm trù nghiệp vụ. Để dễ hình dung, giả sử chúng ta đề nghị người dùng cuối kiểm tra phạm trù nghiệm vụ cho bảng dữ liệu Company, trong giai đoạn "Phân tích yêu cầu nghiệp vụ" (là giai đoạn người thiết kế chương trình phải tiếp xúc người dùng cuối lấy thông tin)
Tên cột Loại dữ liệu Kích thước
CompanyId Ký tự 10
Company phone Xâu ký tự 20
Company fax Xâu ký tự 20
Company hotline Xâu ký tự 20

Đối với các lập trinh viên thì thông tin như trên là quá rõ ràng, nhưng đối với một người sử dụng thông thường thì sẽ là dễ hơn, nếu chúng ta trình bày thế này:

Tên cột Loại dữ liệu
CompanyId Code
Company phone Số điện thoại
Company fax Số điện thoại
Company hotline Số điện thoại

Và người dùng có thể dễ dàng kiểm tra xem thiết kế cơ sở dữ liệu có thể đúng đắn (về mặt logic) hay chưa.

Lý do thứ hai của việc sử dụng DOMA là khuyến khích tính thống nhất về mặt dữ liệu. Trong không ít dự án, chúng ta thường gặp hiện tượng cùng là "phone number", nhưng ở bảng "Company" thì "phone number" được định nghĩa là "CHAR(20)", ở bảng "Employee" thì định nghĩa là "VARCHAR(25)", tùy theo cảm nhận của người tạo Database lúc đó, nên gây ra tình trạng không đồng nhất về mặt dữ liệu. Nay với cách sử dụng domain, thì mỗi domain sẽ được chuyển thành một kiểu dữ liệu vật lý, và cứ thế chuyển vào cấu trúc DB. Ví dụ định nghĩa ánh xạ từ giai đoạn "phân tích yêu cầu nghiệp vụ" sang giai đoạn thiết kế chi tiết - detail design như sau:

Tên domain Loại dữ liệu vật lý
Code CHAR(10)
Phone number CHAR(20)

và tất cả các cột dữ liệu chứa số điện thoại sẽ có cùng kiểu dữ liệu, đảm bảo tính nhất quán trong suốt quá trình thiết kế -> lập trình. Sau khi thiết kế, có thể trực tiếp dùng @Domain để ánh xạ sang kiểu vật lý. Lý do thứ ba của việc sử dụng Doma là tạo sự dễ dàng thông suốt trong các công đoạn khác nhau của quá trình phát triển phần mềm. Người thiết kế sẽ viết các thiết kế chương trình trên các domain (kiểu dữ liệu logic), còn người lập trình sẽ làm việc trên các kiểu dữ liệu vật lý. Ví dụ người viết thiết kế thiệt kế như sau: (viết thể hiện nghiệp vụ cho người dùng cuối xem và đối chiếu)

Nếu [điện thoại công ty].[mã vùng] == "Hà Nội"  .... //viết như thế này thì người dùng cuối sẽ dễ dàng hiểu và đối chiếu được

Lập trình viên sẽ dễ dàng chuyển thành đoạn mã

if(companyPhone.getAreaCode) == "04" .... //căn cứ vào thông tin domain và các chức năng bổ sung của domain ở giai đoạn thiết kế nghiệp vụ.

Như vậy, có thể thấy, khi sử dụng doma, việc chuyển từ định nghĩa logic lúc đối chiếu nghiệp vụ tới công đoạn lập trình sẽ khá thông suốt và thuận lợi.

  1. Hỗ trợ Domain trong các hệ cơ sở dữ liệu Các hệ cơ sở dữ liệu truyền thống như SysBase hay PostgreSQL đều có hỗ trợ domain (và sẽ rất thuận lợi cho việc áp dụng DOMA). Cụ thể để tạo DOMAIN trong postresql, ta có lệnh CREATE DOMAIN (https://www.postgresql.org/docs/9.5/static/sql-createdomain.html) Oracle thì có thể dùng: CREATE TYPE để định nghĩa một kiểu dữ liệu mới. Tuy nhiên ý nghĩa của việc sử dụng DOMAIN (và DOMA) không thay đổi ngay cả khi hệ cơ sở dữ liệu không hỗ trợ trực tiếp.
  2. Những điểm yếu khi sử dụng DOMA Một điểm yếu nhất của DOMA là nó chưa ánh xạ được các quan hệ (1-1, n-n, 1-n, n-1) vào các đối tượng. Cho tới version 2, DOMA không có các annotation như Hibernate (@OneToOne, @OneToMany....) nên việc thể hiện hoàn toàn các quan hệ này từ giai đoạn thiết kế sang giai đoạn lập trình rất khó khăn, tất cả phải dựa vào sơ đồ ERD và lập trình viên phải tự viết các câu lệnh JOIN bảng. Tất nhiên, ở chiều ngược lại, nếu gắn cứng các quan hệ như kiểu Hibernate thì nhiều lúc join bảng lại trở nên khó khăn (không join được các bảng không có mapping liên hệ mà phjari dùng HQL hoặc SQL thuần). Mặt khác, việc thiết kế dữ liệu trừu tượng trước (DOMAIN) mà DOMA khuyến khích, rồi mới tới dữ liệu vật lý sẽ làm tăng thời gian thiết kế. Đói với các dự án cỡ nhỏ, điều này tỏ ra không thích hợp.
0