CHƢƠNG 3 CÀI ĐẶT VÀ THỰC NGHIỆM
3.4. Kết quả thực nghiệm
Bước 1: Chuẩn bị đầu vào
Đầu vào của thực nghiệm là mô hình miền của CourseMan và lớp ModuleMain. Mô hình miền có thể là một mô hình miền chƣa hoàn chỉnh, thông qua việc thực hiện các vòng lặp phát triển, mô hình miền sẽ đƣợc làm phong phú và hoàn thiện.
Hình 3.3. Đầu vào của thực nghiệm
Lớp ModuleMain là một lớp cấu hình mô-đun, chứa các cấu hình chung về lớp điều khiển, giao diện hiển thị, môi trƣờng cài đặt,… của phần mềm đƣợc khởi tạo. @ModuleDescriptor(
name = "ModuleMain", viewDesc = @ViewDesc(
formTitle = "Course Management App: CourseMan", imageIcon = "courseman.jpg",
view = View.class, viewType = Type.Main,
topX = 0.5, topY = 0.0, widthRatio = 0.75f, heightRatio = 1f, children = { RegionName.Desktop, RegionName.MenuBar, RegionName.ToolBar, RegionName.StatusBar }, excludeComponents = { Add }, props = { @PropertyDesc(name = PropertyName.view_toolBar_buttonIconDisplay, valueAsString = "true", valueType = Boolean.class), @PropertyDesc(name = PropertyName.view_toolBar_buttonTextDisplay, valueAsString = "false", valueType = Boolean.class), @PropertyDesc(name=PropertyName.view_searchToolBar_buttonIconDisplay, valueAsString = "true", valueType = Boolean.class), @PropertyDesc(name=PropertyName.view_searchToolBar_buttonTextDisplay, valueAsString = "false", valueType = Boolean.class), @PropertyDesc(name = PropertyName.view_lang_international,
valueAsString = "true", valueType = Boolean.class), }
),
controllerDesc = @ControllerDesc(controller = Controller.class), type = ModuleType.DomainMain,
setUpDesc = @SetUpDesc(postSetUp = CopyResourceFilesCommand.class) )
Mô hình miền của của CourseMain chứa bốn lớp miền. Mã nguồn lớp miền
Student :
@DClass(schema=”courseman”)
public class Student{
/*** STATE SPACE **/
@DAttr(name = “id”, type = Type.Integer, id = true, auto = true, mutable = false, optional = false, min = 1.0)
private int id;
@DAttr(name = “name”, type = Type.String, length =30 , optional = false)
private String name;
@DAttr(name = “address”, type = Type.Domain, length = 20, optional = true) @DAssoc(ascName = “student-has-address”, role = “student”,
ascType = AssocType.One2One, endType = AssocEndType.One,
associate = @Associate(type= Address.class, cardMin = 1, cardMax= 1))
private Address address;
@DAttr(name = “enrolments”, type = Type.Collection, optional = false, serialisable = false, filter = @Select(clazz = Enrolment.class)) @DAssoc(ascName = “std-has-enrols”, role = “student”,
ascType = AssocType.One2Many, endType = AssocEndType.One,
associate= @Associate(type= Enrolment.class, cardMin=0, cardMax= 30))
private Collection<Enrolment> enrolments;
/*** BEHAVIOUR SPACE **/
}
Và mã nguồn lớp Address : @DClass(schema = "courseman")
public class Address {
/*** STATE SPACE **/
@DAttr(name= "id", id= true, auto= true, length= 3, mutable = false, optional = false, type = Type.Integer)
private int id;
@DAttr(name = "cityName", type = Type.String, length = 20, optional = false)
private String cityName;
@DAttr(name="student", type=Type.Domain, optional=true, serialisable=false) @DAssoc(ascName = "student-has-address", role = "address",
ascType = AssocType.One2One, endType = AssocEndType.One,
associate = @Associate(type= Student.class, cardMin = 1, cardMax = 1, determinant = true))
private Student student;
/*** BEHAVIOUR SPACE **/
}
Các lớp miền đƣợc thiết kế chứa các thông tin hƣớng miền và đƣợc mô hình hóa dƣới dạng tập các meta-attribute gắn vào lớp và các thành viên của lớp. Ví dụ đối với lớp Address, DClass xác định schema lƣu trữ các đối tƣợng miền là “courseman”, các thuộc tính khác có giá trị mặc định: serialisable = true : các đối tƣợng miền có thể đƣợc lƣu trữ trong không gian lƣu trữ có tên schema là “courseman”; mutable = true :
các đối tƣợng miền đƣợc hiển thị dƣới dạng chỉ xem nên ngƣời dùng không đƣợc phép thay đổi trạng thái của chúng; singleton = false : có một vài đối tƣợng miền đƣợc tạo ra trong thời gian chạy của phần mềm…
Các lớp miền đầu vào có cấu trúc chƣa hoàn thiện, thiếu các phƣơng thức hoạt động. Các phƣơng thức này sẽ đƣợc tự động sinh ra trong bƣớc tiếp theo.
Bước 2: Sinh phương thức cho lớp miền nhờ BspaceGen
Các phƣơng thức của lớp miền đƣợc sinh ra bằng cách thực hiện: Chọn các lớp miền > chuột phải, chọn Source > chọn Generate BSpace
Hình 3.4: Giao diện menu sinh phương thức cho lớp miền
Kết quả thu đƣợc hai lớp miền hoàn chỉnh. Mã nguồn lớp Address thu đƣợc nhƣ sau: @DClass(schema = “courseman”)
public class Address {
/*** STATE SPACE **/
@DAttr(name = "id", id = true, auto = true, length = 3, mutable = false, optional = false, type = Type.Integer)
private int id;
@DAttr(name = "cityName", type = Type.String, length = 20, optional = false)
private String cityName;
@DAttr(name= "student", type= Type.Domain, optional=true, serialisable= false) @DAssoc(ascName = "student-has-address", role = "address",
ascType = AssocType.One2One, endType = AssocEndType.One,
associate = @Associate(type = Student.class, cardMin = 1, cardMax = 1, determinant = true))
/*** BEHAVIOUR SPACE **/
private static int idCounter;
@DOpt(type = DOpt.Type.Getter) @AttrRef(value = "id")
public int getId() {}
@DOpt(type = DOpt.Type.AutoAttributeValueGen) @AttrRef(value = "id")
private static int genId(Integer id) {...}
@DOpt(type = DOpt.Type.Getter) @AttrRef(value = "cityName")
public String getCityName() {...}
@DOpt(type = DOpt.Type.Setter) @AttrRef(value = "cityName")
public void setCityName(String cityName) {...}
@DOpt(type = DOpt.Type.Getter) @AttrRef(value = "student")
public Student getStudent() {...}
@DOpt(type = DOpt.Type.Setter) @AttrRef(value = "student")
public void setStudent(Student student) {...}
@DOpt(type = DOpt.Type.LinkAdderNew) @AttrRef(value = "student")
public boolean setNewStudent(Student obj) {
setStudent(obj); return false; }
@DOpt(type = DOpt.Type.DataSourceConstructor)
public Address(Integer id, String cityName) throws
ConstraintViolationException { this.id = genId(id);
this.cityName = cityName; this.student = null; }
@DOpt(type = DOpt.Type.ObjectFormConstructor)
public Address(String cityName, Student student) throws
ConstraintViolationException { this.id = genId(null);
this.cityName = cityName; this.student = student; }
@DOpt(type = DOpt.Type.RequiredConstructor)
public Address(String cityName) throws ConstraintViolationException {
this.id = genId(null); this.cityName = cityName; this.student = null; }
}
Lớp miền Address mới có ba hàm khởi tạo, một hàm getter và hàm tự động sinh giá trị cho thuộc tính id; các hàm getter và setter cho thuộc tính cityName; và ba
thức đầu là hàm getter và setter, phƣơng thức cuối liên quan đến việc thêm mới các đối tƣợng Student kết hợp với Address. Các phƣơng thức tạo có chứa meta-attribute
tên là Dopt xác định loại phƣơng thức.
Loại tham số đƣợc sử dụng trong mỗi hàm khởi tạo phải là kiểu đối tƣợng. Hàm khởi tạo sử dụng hàm gentID, tự động tạo ra giá trị tiếp theo cho thuộc tính id. Hàm genID này sử dụng một thuộc tính tĩnh khác là idCounter để giữ giá trị lớn nhất đến
thời điểm hiện tại của id. Thuộc tính id, không có hàm setter bởi vì nó có
DAttr.mutable = false nghĩa là giá trị của nó không đƣợc thay đổi bởi ngƣời dùng. Phƣơng thức setNewStudent thuộc loại Dopt.Type.LinkAdderNew đặc tả cho liên kết 1:1 giữa Student và Address nghĩa là thêm một liên kết mới từ đối tƣợng
Student tới đối tƣợng Address đã tồn tại hoặc vừa tạo.
Bước 3: Sinh tập cấu hình mô-đun phần mềm, được xây dựng từ mô hình lớp miền.
Cách thực hiện: chọn các lớp miền sinh ra từ bƣớc 2 > chuột phải, chọn Source > chọn Generate MCC :
Hình 3.5: Giao diện menu sinh cấu hình mô-đun phần mềm
Các cấu hình mô-đun phần mềm đƣợc sinh ra sẽ nằm trong gói modules và có
Hình 3.6: Cấu hình mô-đun phần mềm được sinh ra
Mã nguồn cấu hình mô-đun phần mềm ModuleAddress, đƣợc xây dựng từ lớp miền
Address. ModuleAddress là một lớp có kiến trúc dựa trên mô hình MVC bao gồm ba thành phần: một mô hình (lớp miền), một khung nhìn hiển thị giao diện ngƣời dùng và một lớp điều khiển. Mỗi lớp mô-đun có phạm vi trạng thái là một tập con của các thuộc tính của lớp miền đƣợc định nghĩa trong đặc tả mô-đun, ở đây là lớp Address. @ModuleDescriptor(
name = "ModuleAddress",
modelDesc = @ModelDesc(model = Address.class), viewDesc = @ViewDesc(
formTitle = "Form: Address", imageIcon = "Address.png", domainClassLabel = "Address", parent=RegionName.Tools,
view = View.class, viewType=Region.Type.Data), controllerDesc = @ControllerDesc())
public class ModuleAddress {
@AttributeDesc(label = "title")
private String title;
@AttributeDesc(label = "id")
private int id;
@AttributeDesc(label = "cityName")
private String cityName;
@AttributeDesc(label = "student")
private Student student;
}
Bước 4: Sinh tập cấu hình phần mềm từ tập cấu hình mô-đun phần mềm.
Cách thực hiện: chọn ModuleMain và các cấu hình mô-đun phần mềm sinh ra từ bƣớc 3 > chuột phải, chọn Source > chọn Generate SWC. Đối với vòng lặp thứ nhất
sử dụng 3 cấu hình mô-đun: ModuleAddress, ModuleEnrollment, ModuleStudent.
Hình 3.7: Giao diện menu sinh cấu hình phần mềm
Cấu hình phần mềm đƣợc sinh ra sẽ nằm trong gói software.config và có tên đƣợc đặt theo nguyên tắc SWC + Số thứ tự. Mỗi vòng lặp phát triển sử dụng các lớp cấu hình mô-đun phần mềm khác nhau để tạo ra cấu hình riêng cho mỗi nguyên mẫu phần mềm. Một biến tự động tăng sinh số thứ tự cho các cấu hình phần mềm. Ví du, trong vòng lặp thứ nhất, cấu hình phần mềm có tên là SWC1, vòng lặp thứ hai là SWC2.
Mã nguồn cấu hình phần mềm SWC1 là một lớp rỗng, có chứa các meta-attriubute mô tả chi tiết thông tin nguyên mẫu phần mềm đƣợc tạo ra nhƣ tên ứng dụng, ngôn ngữ sử dụng, thông tin về tổ chức, …Lớp cấu hình phần mềm SWC1 cũng có kiến trúc dựa trên mô hình MVC với ba thành phần chính là một mô hình (các lớp mô-đun chứa lớp miền), một khung nhìn mô tả giao diện ngƣời dùng và một lớp điều khiển. Phần mô hình chứa các lớp mô-đun sử dụng: ModuleMain, ModuleAddress, ModuleStudent,
ModuleEnrollment.
@SystemDesc(
appName = "Courseman",
splashScreenLogo = "coursemanapplogo.jpg", language = Language.English,
orgDesc = @OrgDesc(name = "Faculty of IT",
address = "K1m9 Nguyen Trai Street, Thanh Xuan District", logo = "hanu.gif",
url = "http://localhost:5432/domains"), dsDesc = @DSDesc(type = "postgresql",
dsUrl = "//localhost:5432/domainds", user = "admin", password = "password", dsmType = DSM.class, domType = DOM.class,
osmType = PostgreSQLOSM.class, connType = ConnectionType.Client), modules = { ModuleMain.class, ModuleAddress.class, ModuleStudent.class,
ModuleEnrollment.class },
sysModules = {},
setUpDesc = @SysSetUpDesc(setUpConfigType = SetUpConfig.class), securityDesc = @SecurityDesc(isEnabled = false))
public class SWC1 {}
Làm tƣơng tự đối với vòng lặp phát triển thứ hai, sử dụng cấu hình mô-đun phần mềm
ModuleAddress, thu đƣợc cấu hình phần mềm SWC2:
Hình 3.9: Cấu hình phần mềm SWC2 được sinh ra
Cấu hình phần mềm SWC2 có mã nguồn chỉ sử dụng cấu hình mô-đun ModuleMain và ModuleAddress :
@SystemDesc(
appName = "Courseman",
splashScreenLogo = "coursemanapplogo.jpg", language = Language.English,
orgDesc = @OrgDesc(
name = "Faculty of IT",
address = "K1m9 Nguyen Trai Street, Thanh Xuan District", logo = "hanu.gif",
url = "http://localhost:5432/domains"), dsDesc = @DSDesc(
type = "postgresql",
dsUrl = "//localhost:5432/domains", user = "admin", password = "password", dsmType = DSM.class, domType = DOM.class,
osmType = PostgreSQLOSM.class, connType = ConnectionType.Client), modules = { ModuleMain.class, ModuleAddress.class },
sysModules = {},
setUpDesc = @SysSetUpDesc(setUpConfigType = SetUpConfig.class), securityDesc = @SecurityDesc(isEnabled = false))
public class SWC2 {
}
Thực hiện một cách tƣơng tự cho các vòng lặp phát triển khác, sẽ thu đƣợc các cấu hình phần mềm riêng cho các nguyên mẫu phần mềm. Bƣớc tiếp theo sẽ là sử dụng công cụ DomainAppTool để sinh ra nguyên mẫu phần mềm.
Bước 5: Sinh phần mềm từ tập cấu hình phần mềm
Tạo lớp CourseMan có hàm main khai báo lớp cấu hình phần mềm SWC1.class đã sinh và lớp thiết lập môi trƣờng cài đặt mặc định SetupGen.class.
public class CourseMan {
public static void main(String[] args) {
final Class SwCfgCls = SWC1.class;
final Class SetUpCls = SetUpGen.class;
SoftwareAio sw = new SoftwareStandardAio(SetUpCls, SwCfgCls); try { sw.exec(args); } catch (NotPossibleException e) { e.printStackTrace(); System.exit(1); } } }
Tạo cấu hình JVM mặc định khi chạy mã nguồn: chuột phải CourseMan >
RunAs > Run Configurations. Màn hình Run Configurations hiện ra. Trong panel
Hình 3.10: Cấu hình Run Configurations để chạy mã nguồn
Trong Arguments của panel chính, khai báo hai tham số: -Dlogging=true kích hoạt
chức năng log hệ thống, tham số thứ hai thiết lập đối tƣợng miền sinh ra sẽ đƣợc lƣu trong bộ nhớ chạy thay vì lƣu trong cơ sở dữ liệu.
Chạy chƣơng trình: chuột phải CourseMan > Run As > Run Configurations.
Màn hình Run Configurations hiện ra > chọn cấu hình CourseMan vừa tạo > cuối
cùng chọn Run. Giao diện phần mềm CourseMan đƣợc sinh ra nhƣ sau:
Vòng lặp phát triển đầu tiên sử dụng ba mô-đun cấu hình phần mềm, tƣơng ứng với nó là ba form giao diện đƣợc sinh ra. Ba form này nằm ở trên menu Tools. Nhấn chuột vào từng form, giao diện hiển thị các thông tin về lớp miền tƣơng ứng hiện ra. Trong mỗi form, các thuộc tính đƣợc cấu hình mặc định, ví dụ tiêu đề của form vẫn để là “title” hay tên các thuộc tính vẫn giữ nguyên nhƣ trong lớp miền. Nguyên nhân là do hiện tại các cấu hình mô-đun chứa các meta-attribute đính kèm với lớp hoặc thành viên của lớp dƣới dạng các annotation, nếu không đƣợc thay đổi thủ công bằng tay thì phần mềm sẽ lấy các giá trị mặc định của cấu hình mô-đun.
Hình 3.12: Giao diện các form của CourseMan trong vòng lặp đầu tiên
Thay đổi cấu hình mô-đun của lớp Address bằng cách sửa giá thuộc tính label trong
mã nguồn ModuleAddress : @ModuleDescriptor(
name = "ModuleAddress",
modelDesc = @ModelDesc(model = Address.class), viewDesc = @ViewDesc(
formTitle = "Form: Address", imageIcon = "Address.png", domainClassLabel = "Address", parent=RegionName.Tools,
view = View.class, viewType=Region.Type.Data), controllerDesc = @ControllerDesc())
public class ModuleAddress {
@AttributeDesc(label = "Địa chỉ")
@AttributeDesc(label = "Mã thành phố")
private int id;
@AttributeDesc(label = "Tên thành phố")
private String cityName;
@AttributeDesc(label = "Sinh viên")
private Student student;
}
Chạy lại phần mềm, giao diện CourseMan chứa form Address có các nhãn thuộc tính bằng tiếng Việt:
Hình 3.13: Giao diện các form của CourseMan sau khi sửa cấu hình mô-đun
Làm tƣơng tự đối với các vòng lặp phát triển tiếp theo, các nguyên mẫu phần mềm CourseMan mới đƣợc sinh ra. Kiểm thử trên từng nguyên mẫu phần mềm, nhà phát triển phát hiện các lỗi xảy ra, các thuộc tính thiếu và quay trở lại thay đổi lớp miền. Thông qua các vòng lặp, kết quả cuối cùng thu đƣợc một lớp miền tối ƣu nhất cho phần mềm.
Trƣớc đây, sau khi chỉnh sửa, các lớp miền phải đƣợc đóng gói dƣới dạng .jar và phải sử dụng các lệnh command line trong terminal để thực hiện sinh ra nguyên mẫu phần mềm. Cứ nhƣ thế, mỗi lần thay đổi lớp miền, công việc này lại đƣợc lặp lại gây khó khăn cho các nhà phát triển phần mềm. Nhƣ vậy, nhờ có plug-in việc thực hiện các vòng lặp không những đơn giản hơn mà còn rất hữu ích trong việc xem và thay đổi cấu hình mô-đun phần mềm.
3.5. Tổng kết chƣơng
Kết quả thực nghiệm đã chứng minh công cụ plug-in giúp cho việc phát triển phần mềm hƣớng miền trở nên dễ dàng hơn. Các vòng lặp phát triển đƣợc thực hiện đơn giản thông qua nhấn chuột thay vì thực hiện một loạt các lệnh command line. Tuy nhiên, hệ thống vẫn còn ba hạn chế chính: một là phần mềm đƣợc thêm ra mới chỉ dừng lại ở các chức năng cơ bản là thêm, sửa, xóa; hai là việc thay đổi cấu hình mô- đun phần mềm hiện tại vẫn phải thực hiện thủ công bằng cách thay đổi trực tiếp mã nguồn mà số lƣợng thuộc tính nhiều, sẽ gây không ít khó khăn cho nhà phát triển; ba là chƣa kiểm tra tính đúng đắn của lớp miền, cấu hình mô-đun phần mềm và cấu hình phần mềm (hiện tại, chủ yếu thực hiện kiểm thử phần mềm sinh ra có lỗi không).
KẾT LUẬN VÀ HƢỚNG PHÁT TRIỂN
Trong phát triển phần mềm, phƣơng phát tiếp cận phát triển hƣớng miền đƣợc sử dụng cho những nhu cầu phức tạp, kết nối cài đặt với một mô hình đang phát triển của các khái niệm nghiệp vụ. DDD bao gồm một ngôn ngữ chung, các kỹ thuật và mô hình cũng nhƣ một kiến trúc; tập trung vào mô hình hóa các vấn đề cần giải quyết. Với DDD, các nhà phát triển có đƣợc những kỹ thuật giúp tối thiểu hóa sự phức tạp và thúc đẩy sự cộng tác với các chuyên gia miền. Với những ƣu điểm vƣợt trội của mình, DDD thực sự là một phƣơng pháp phát triển phần mềm hiện đại và hữu ích.
Công cụ hỗ trợ phát triển phần mềm hƣớng miền đƣợc thiết kế dựa trên các nguyên tắc DDD, giúp tự động sinh ra phần mềm từ các mô hình lớp miền. Nhờ đó, mà ngƣời dùng chỉ cần tập trung vào miền vấn đề và mô hình miền, toàn bộ phần mềm (bao gồm giao diện và lƣu trữ đối tƣợng) sẽ đƣợc sinh ra một cách tự động và nâng cao hiệu xuất sản xuất phần mềm. Mặc dù, chức năng các phần mềm đƣợc tạo ra bởi công cụ, còn đơn giản nhƣng công cụ đã giúp cho thiết kế hƣớng miền DDD lại gần hơn với các nhà phát triển phần mềm. Một trong những hạn chế của công cụ là không có giao diện trực quan, dẫn đến những khó khăn cho ngƣời dùng cũng nhƣ tốn nhiều thời gian trong quá trình sử dụng. Luận văn này đã giải quyết đƣợc hạn chế này bằng việc xây dựng một ứng dụng Eclipse plug-in. Về mặt lý thuyết, ứng dụng này đƣợc xây dựng từ ba thuật toán là thuật toán sinh phƣơng thức cho phần mềm, thuật toán sinh cấu hình mô-đun phần mềm và cuối cùng là thuật toán sinh cấu hình phần mềm. Ứng dụng này là một phần của Eclipse và đƣợc tích hợp trực tiếp vào Eclipse. Qua đó, bất kì ngƣời dùng nào sử dụng công cụ có thể tải về và cài đặt vào Eclipse của mình để sử dụng. Điều này có ý nghĩa quan trọng giúp cho công cụ hỗ trợ phát triển phần mềm hƣớng miền đƣợc sử dụng rộng rãi hơn.
Tuy nhiên, trong luận văn mới chỉ dừng lại ở việc xây dựng đƣợc ứng dụng Eclipse plug-in giúp trực quan hóa việc sử dụng công cụ hỗ trợ phát triển phần mềm