Thing

Tôi vẫn loanh quanh với cái ý này nên phải tiếp tục.

Các hệ thống thông tin thường định nghĩa các loại đối tượng mang nội dung, với một số thuộc tính.Chẳng hạn một hệ thống quản lí dự án (tôi đang làm một cái đại loại vậy, nên các ví dụ sẽ dính tới nó) có thể có các đối tượng test case, requirement. Với một số hệ thống người dùng có thể thêm các thuộc tính mới vào. Điều này giúp tăng cường khả năng thích nghi của hệ thống với những yêu cầu khác nhau. Các hệ thống dạng này có những điểm chung và riêng, nhưng bất kể là chung hay riêng thì khi phát triển chúng ta cứ phải làm đi làm lại, và giải quyết những vấn đề giống nhau. Tôi muốn tìm kiếm một cách nhìn cho những điểm chung và riêng như vậy, theo một hướng khác, biết đâu sẽ giúp ích cho những dự án sau này.

Thường thì các hệ thống sẽ đi từ loại đối tượng (từ giờ tôi sẽ gọi ngắn gọn là type, như test case, requirement, bug, note, …). Hệ thống hoặc người dùng sẽ định nghĩa các type, và khi tạo một đối tượng người ta phải chọn type trước. Tôi muốn thử đi ngược lại, đối tượng có trước, type có sau (lí do sẽ được trình bày dần). Chúng ta sẽ gọi các đối tượng mang nội dung trong hệ thống là thing (tiếng Anh cho dễ viết mã). Mỗi thing thì gồm nhiều tính chất (gọi là property). Điều này ứng với thực tế là khi nhìn một đồ vật người ta sẽ ghi nhận tính chất của chúng. Property là một bộ gồm tên (key) và giá trị (value), key thì phải là dạng chuỗi, còn value thì có thể là chữ, số, ngày, hay một dạng phức tạp hơn, tương tự như mỗi tính chất của đồ vật thì có rất nhiều từ để mô tả nó.

Khi người dùng tạo thing thì họ có thể tạo và điền các property của nó theo ý của mình. Đôi khi chúng ta muốn thing tạo ra có sẵn các property và value nhất định. Cách tiếp cận thông thường là các hệ thống cho người dùng tạo một type mới, định nghĩa các property và value mặc định của chúng. Tuy nhiên, khi làm như vậy, các value mặc định sẽ đứng đơn lẻ chứ không phải hợp với nhau để thể hiện một thing. Chẳng hạn những ràng buộc như “ngày bắt đầu” phải nhỏ hơn “ngày kết thúc” sẽ không thể được định nghĩa hay kiểm tra khi tách riêng hai property này. Nói cách khác, cái chúng ta cần không phải là giá trị mặc định của một property, mà là một thing mặc định. Một hạn chế nhỏ nữa là với thiết kế dạng này, chúng ta không thể tạo sẵn nhiều hơn một khuôn mẫu (những bộ value mặc định) cho thing. Để giải quyết vấn đề này chúng ta sẽ đưa ra khái niệm sao chép (clone) và khuôn mẫu (template).

Với các thing đã tạo, người dùng có thể clone để tạo ra các thing mới. Tất nhiên, thing tạo ra bởi clone sẽ có property và value giống như thing được clone. Chúng ta sẽ gọi thing được clone là template. Template chỉ là một cách gọi, còn về bản chất chúng cũng là thing. Như vậy chúng ta có thể đưa ra định nghĩa type mới. Type chính là template của một thing. Ví dụ, thing C và thing D được clone từ template thing B thì chúng cùng có type A. Type ở đây không cần là khái niệm chính thức, mà chỉ để giúp chúng ta đối chiếu với các hệ thống đã có. Type có thể thành một chuỗi, ví dụ template thing B có thể được tạo từ template thing A. Có thể chúng ta sẽ có một thing gốc, ví dụ như THING. Từ THING chúng ta sẽ tạo ra các thing và các thing này có thể được dùng làm template để tạo ra các thing khác.

Có thể xét một ví dụ để làm rõ ý này. Cho một hệ thống quản lí dự án. Chúng ta sẽ tạo một thing là Artifact với một property là name. Dùng Artifact làm template chúng ta clone ra Test Case, Requirement và thêm những property khác. Khi đấy việc hiện thực chứng năng tìm kiếm sẽ được đơn giản hóa, vì tìm kiếm trên Test Case, Requirement, hay toàn bộ Artifact thì cũng chỉ là tìm kiếm trên thing mà thôi. Hơn nữa, nếu sau này tất cả các đối tượng trong dự án phải có thêm property mới như ID chẳng hạn, chúng ta chỉ cần cập nhật thing Artifact.

Tôi đã mượn từ clone trong JavaScript rồi, tôi sẽ mượn thêm ý tưởng duck typing (“When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck”). Bởi vì chúng ta không có type, mọi chức năng sẽ khá mềm dẻo. Những chức năng nghiệp vụ giờ đây không cần quan tâm đến type của thing, mà chỉ cần biết chúng thỏa mãn một tập điều kiện cho trước. Chẳng hạn, chức năng report chỉ cần quan tâm là tập thing có property status hay không, và status có giá trị gì, v.v là có thể đưa ra thống kê cho tập thing này rồi. Việc giới hạn chức năng hoạt động theo type (hay chính xác hơn là template mà một thing được clone từ đó) vẫn có thể thực hiện được, nhưng nó chỉ là một tham số mà thôi. Cách tiếp cận này sẽ giúp chúng ta xây dựng nhanh chóng những chức năng giống nhau cho các type khác nhau.

Về mặt hiện thực, cần chú ý là các thing có thể hoàn toàn khác nhau về số lượng hay kiểu property, nên cấu trúc database phải loại bỏ hoàn toàn khái niệm về type như thông thường. Về việc thiết kế kiến trúc, có lẽ phải tìm cách để module hóa càng nhiều càng tốt, khi đó các khái niệm xung quanh thing có thể được hiện thực thêm mà ít gây ảnh hưởng cho toàn hệ thống. Có thể là thing sẽ được thể hiện trong database hay mã dưới dạng associate array, gồm các cặp key và value. Các cặp key và value này không nhất thiết phản ánh 1:1 các property. Chẳng hạn cho một property dạng tag với value “new, unfinished”, chúng ta sẽ có trong thing các cặp (key, value) là (p_count, 2), (p_tag_1, “new”), (p_tag_2, “value”). Cách thể hiện này ít nhất sẽ giúp việc đọc ghi với database được thống nhất, và các module sẽ không cần tạo ra những cấu trúc dữ liệu phức tạp. Tôi sẽ quay lại chuyện này nếu tôi thực sự bắt đầu hiện thực :D.

Hiện tại chỉ có thế. Còn nhiều vấn đề nữa mà tôi sẽ phải suy nghĩ, trước mắt là:

  • Xem xét khái niệm actor dựa trên thing. Actor là một dạng thing đặc biệt có khả năng kích thích một hành động của hệ thống. Actor có thể là người dùng, một hệ thống khác, hoặc chính bản thân hệ thống này.
  • Xem xét một cơ chế để chia vùng cho thing (zone). Chẳng hạn hệ thống có thể phân cấp theo công ty (company), dự án (project), và nhân viên (user). Một hệ thống khác lại muốn phân chia theo nhóm (group) và thành viên (member/user). Có lẽ các cấp này sẽ được quy về zone, với company, project, user, group, v.v là các property của zone.
  • Xây dựng một cơ chế nào đó cho chức năng security. Mục tiêu là có thể tắt bật “bức tường” security này tùy ý mà hệ thống vẫn hoạt động bình thường.
  • Và mở rộng hơn, là một cơ chế module phù hợp để xây dựng các chức năng dựa trên nền cơ bản này.

Tôi nghĩ xây dựng khái niệm là điều quan trọng khi bắt đầu xây dựng hệ thống. Cho dù cấu trúc của nó có thế nào, thì việc đầu tiên có lẽ là phải gọi tên được một cách chính xác và ngắn gọn các khái niệm sẽ xuất hiện trong mã nguồn của bạn.

Tag

Chỉ là một suy nghĩ chơi, có thể trùng với chỗ nào đó rồi.

Liệu chúng ta có nên nhìn một số kiểu input và property dưới dạng tag. Hãy lấy một ví dụ cụ thể. Tôi có một hệ thống quản lí test case và feature với những thuộc tính như:

  • Mỗi test case có một status, như new, abandoned, finished.
  • Mỗi test case có thể ứng (liên kết) với nhiều feature.
  • Mỗi test case có thể có nhiều tập tin đính kèm.
  • Mỗi feature có thể ứng (liên kết) với nhiều test case
  • v.v

Để ý rằng những tính chất này khá là động, theo nghĩa nó có thể thêm vào bớt đi tùy nhu cầu của người dùng hay người phát triển, và mỗi người dùng lại muốn một cách thiết lập khác nhau.

Chúng ta có thể nhìn các khái niệm trên như sau:

  • Mỗi test case hay feature đều là một sự vật, và đều được mô tả bởi các tính chất.
  • Mỗi property của test case hay feature, như status hay tập tin đính kèm, đều là những tính chất.
  • Các property đều có giá trị, tập giá trị có loại là vô hạn (như text), có loại là cố định (như một tập các check box). Những giá trị này là những “tính từ” mô tả tính chất.
  • Mỗi người dùng hay nhóm người dùng có thể tạo các tập property khác nhau. Ta sẽ coi như đây là các ngôn ngữ khác nhau. Mỗi ngôn ngữ có một bộ từ vựng riêng.

Trước hết, cần hiểu rằng mỗi sự vật đều có tính chất. Nhưng mối quan tâm và cách mô tả sự vật của mọi người đều khác nhau, nên phải tách tính chất chủ quan ra khỏi bản thân sự vật. Trong trường hợp này, chúng ta phải thiết kế để test case hay feature giữ những thuộc tính tối thiểu, và những tính chất cũng test case hay feature sẽ được giữ ở một nơi hay module khác.

Lúc này ta có thể tìm cách xây dựng một cấu trúc chung cho tất cả các property với tập giá trị hữu hạn (vô hạn cũng có thể, nhưng tạm lấy hữu hạn cho dễ nói). Những giá trị này giống như tag (nên tôi đặt tên bài viết là tag). Trong đó mấu chốt là xây dựng tính từ sao cho nó có thể đại diện cho mọi loại đối tượng, cụ thể như:

  • Những tính từ mà người dùng thoải mái thêm vào, như status của test case (new, abandoned, v.v).
  • Những tính từ ứng với đối tượng trong hệ thống. Ví dụ như property thể hiện các feature được liên kết với test case. Mỗi feature chính là một tính từ, cho nên tính từ được sinh ra theo đối tượng.

Khi ta xây dựng được một loại tính từ có thể đại diện cho mọi loại đối tượng, thì việc viết các input cho giao diện cũng sẽ được quy về những loại chung, chẳng hạn:

  • Input cho tối đa n giá trị, n từ 1 tới vô cùng.
  • Input dạng check box, radio, drop down list. Nói chung là dạng mà mọi giá trị được liệt kê.
  • Input dạng search, người dùng sẽ tìm kiếm tại chỗ các tính từ mà họ muốn, đặc biệt là những tính từ ứng với đối tượng trong hệ thống.

Điều này cũng sẽ giúp ta đơn giản hóa việc xây dựng một engine cho việc query dữ liệu. Ví dụ ta có thể đưa ra một cú pháp chung là “<property> has value <adjective>”. Việc query chỉ thực hiện trên tập tính chất và tính từ, nên ta sẽ không phải viết code để xử lí cho từng thuộc tính riêng.

Cuối cùng, chúng ta cần khả năng tách biệt giữa các loại ngôn ngữ và từ vựng, để đảm báo người dùng sẽ có được sự tự do khi làm việc với hệ thống, và người phát triển sẽ không phải nghĩ nhiều về việc người dùng cần gì.

Cứ phải là REST sao

RESTful web services là xu hướng mới của thời đại, trong nước và ngoài nước. Trong khi người tạo ra REST và những nhà REST lí thuyết thì số vẫn đang bận rộn chỉ ra những API đội lốt RESTful thì những bộ API dạng REST vẫn liên tục xuất hiện. REST gần như là “chuẩn”, tức là nói tới việc viết API là mọi người sẽ nghĩ “viết REST”.

REST cũng tương tự Agile. Chúng có những lí thuyết riêng. Những lí thuyết này làm nên sức mạnh của chúng. Nhưng bản thân chúng thì lại ẩn sau vẻ ngoài dễ hiểu và đơn giản. Kết quả là những lí thuyết này thường không được tìm hiểu đầy đủ. Vậy nên những thứ không có quy tắc gì thì được gắn vào chúng. Viết web services dùng HTTP là viết REST. Làm phần mềm mà không có tính toán suy nghĩ gì thì tức là làm Agile.

Điều cần nói đầu tiên về REST là sự đơn giản giả tạo mà nó thể hiện. Điển hình là REST có vẻ gần gũi và tương đồng với HTTP. Người thiết kế REST quá chăm lo cho việc thiết kế media type, cách thể hiện tài nguyên (JSON hay XML, có field này hay không có field kia, v.v). Vấn đề thứ nhì là họ quá tập trung vào thiết kế URI cho đẹp, cho RESTful. Trong khi đó thì REST không quy định gì về URI hay JSON hay XML cả.

Trong khi đó hai thứ quan trọng mà người làm REST lại thường không để ý. Thứ nhất là URI của resources. URI tuyệt đối, chứ không phải ID như trong database, là cách mà REST dùng để định vị một resource. Thứ hai là HATEOAS, tức là hành vi của REST client của bạn trên mỗi resource phụ thuộc vào những hypermedia trong mỗi resource. Hypermedia là xương sống cho sự mềm dẻo và linh động và REST mang lại. Đây là những khái niệm không hiển nhiên nên đòi hỏi phải có sự tìm hiểu kĩ càng.

(Có một ví dụ kiểu tương tự mà bạn có thể sẽ hình dung đến khi tìm hiểu những khái niệm này. Khi bạn vào một tờ báo, chẳng hạn vnexpress.net, bạn sẽ thấy một danh sách tóm tắt các bài mới. Bạn đọc bài viết đầy đủ bằng cách bấm vào liên kết. Bạn không cần quan tâm ID của bài báo là gì, hay URI pattern của trang web là như thế nào. Bạn chỉ cần biết một điểm cuối duy nhất là vnexpress, từ đó bạn có thể đi khắp nơi. Khi bạn đọc một bài báo, bạn có thể bình luận hoặc không bình luận được, tùy vào sự hiện diện của form bình luận. Bạn không cần nhận về một field nào đó thể hiện điều này, hay phải thử bình luận rồi nhận về một status nào đó như 200, 400, 404, 500, v.v. Không chỉ bình luận, tất cả mọi thứ như in, chia sẻ lên mạng xã hội, v.v bạn biết là bạn làm được hay không làm được dựa vào sự hiện diện của nó ngay tại trang báo. Thứ bạn cần biết là cách nhận dạng những thành phần này, như biểu tượng máy in, biểu tượng facebook, v.v.)

Điều cần nói thứ hai, REST chỉ là công cụ, không phải đích đến. Và đến cuối ngày, REST không phải là thứ quan trọng. Tôi từng phải viết client để consume web services của một số phần mềm như BugZilla hay Jira. Điều mà tôi nhận ra, là API không bao giờ đủ. Có những nhu cầu hết sức bình thường và đơn giản thì API lại không có. Điều thứ hai tôi nhận ra là tôi cần làm dễ, làm nhanh chứ không cần cái “trình độ” của một API. Thế nên REST với tôi không tuyệt vời bằng một thư viện client, nơi tôi chỉ cần gọi hàm và nhận kết quả ngay trên ngôn ngữ lập trình mà tôi sử dụng. Vì vậy API tốt là API đầy đủ, dễ dùng, và có tài liệu tốt.

Và bây giờ, nếu bạn muốn tìm hiểu/tìm hiểu lại về REST thì đây là những thứ đáng đọc:

Luận án đã sinh ra REST

RESTful web services

REST in Practice: Hypermedia and Systems Architecture—cuốn này viết không chính xác lắm về REST, nhưng lại có đi sâu vào một số phần của HTTP mà chúng ta sẽ hay dùng khi viết REST over HTTP. Lâu lâu mới có một cuốn O’Reilly mà bìa không in hình thú vật.

Practical API Design: Confessions of a Java Framework Architect—vì phải biết xây dựng API tốt thì mới biết cách ứng dụng REST cho hợp lí

– Và tìm hiểu những bộ web services tốt nhất

UX cho người không chuyên

Tôi thấy UX là một vấn đề vừa bị xem nhẹ vừa bị thổi phồng. Xem nhẹ ở chỗ dường như ai cũng hiểu và lí luận về UX mà không cần tham khảo ý kiến xung quanh, ngay cả khi chưa từng biết hay tìm hiểu về những kiến thức liên quan. Thổi phồng là ở chỗ người ta quá coi trọng nó, chăm chút nó, tới nỗi tiêu tốn một phần không nhỏ thời gian cho những cuộc họp dài lê thê hay sửa đi sửa lại một chức năng nhỏ. Nếu dự án thiếu đi một người thực sự giỏi về UX để cầm trịch, thì những điều nói trên sẽ càng trầm trọng và đe dọa sự thành công của phần mềm.

Như vậy nên làm thế nào nhỉ? Tôi không biết, vì những gì tôi gặp phải chỉ là những kinh nghiệm rời rạc trong từng tình huống cụ thể, chưa đủ để tạo thành một tư tưởng gì đó thật đúng đắn. Bạn mà làm lập trình viên thì bạn cũng sẽ phải làm đi làm lại một chức năng cho UX nó tốt hơn thôi. Tuy nhiên, dưới con mắt lập trình viên, tôi nghĩ rằng với những nhóm nhỏ không chuyên về UX, thì điều quan trọng là làm cho phần mềm ổn định, logic, và linh động (hay nói cách khác là dẹp UX qua một bên).

Để có UX tốt thì trước hết phải chạy được đã, tức là phần mềm phải ổn định. Để phần mềm ổn định thì nhất định không được làm những thứ quá khó, đặc biệt là những thành phần trên giao diện web trong thời đại mà JavaScript là tất cả. Tôi biết là lập trình viên ham thử thách, và không có gì là không thể khi ta quyết tâm, v.v nhưng mà đôi khi phải nhìn nhận thực tế là chúng ta không đủ trình độ để làm mọi thứ, và nhìn ra thứ gì khó làm, không làm được, hoặc dễ gây lỗi cũng là một cách thể hiện trình độ. Với người viết yêu cầu cho phần mềm, cần nhìn ra bản chất của một yêu cầu để tìm ra cách thể hiện phù hợp và khả thi, nhất là tránh bị cuốn vào những hành vi tủn mủn trên giao diện mà quên mất chính flow bên dưới mới là thứ quyết định UX tốt hay không.

(Đấy là chưa kể thời gian cho việc phát triển một phần mềm luôn là có hạn, cho nên hệ quả là thời gian cho nhiều thứ quan trọng không kém như kiến trúc, unit test, build, deploy, v.v sẽ bị ăn bớt. Kiến trúc là thứ cần ổn định, mà một khi phần mềm đã cho dùng đại trà thì việc thay đổi nó sẽ khó khăn hơn. Còn yêu cầu về chức năng, UX, v.v thì luôn cần thử nghiệm và đổi mới, cho nên sẽ không hợp lí nếu tập trung công sức và thời gian vào nó trong giai đoạn đầu.)

(Nhưng ổn định không phải là không có gì. Bạn có nhận thấy vào buổi đầu của những ứng dụng web, người ta cố gắng nhái theo những phần mềm trên desktop, điển hình là các web mail. Kết quả là chúng ta có những ứng dụng đầy đủ chức năng, nhưng cồng kềnh, chậm chạp, nhiều lỗi. Về sau này, người ta lại có xu hướng tạo ra những ứng dụng cực kì đơn giản, rồi bổ sung dần tính năng song song với việc đảm bảo tính ổn định. Mọi chức năng đều cần được đưa vào đúng thời điểm.)

Nếu chúng ta không đủ trình độ hay dữ kiện để hiểu được người dùng mong đợi gì, thì hãy cố gắng làm do mọi chức năng logic nhất có thể. Việc không làm người dùng bất ngờ thì sách báo hay nói rồi, tôi cũng không dám ý kiến gì nhiều. Nhưng ít nhất yêu cầu chức năng không được làm lập trình viên bất ngờ. Tính logic sẽ giúp lập trình viên giữ code "ngăn nắp" và ít lỗi. Không nên để lập trình viên phải xoay sở với những sơ đồ trạng thái lòng vòng đầy những kịch bản tủn mủn và khó xảy ra. Chẳng hạn, những thứ mà lập trình viên có thể đoán đúng mà chưa cần đọc yêu cầu, hay người kiểm tra có thể nhớ được dễ dàng thì thường có logic tốt.

(Tôi định viết một vài ví dụ trong dự án tôi làm, nhưng có vẻ dài dòng không cần thiết. Có vẻ các dự án mới thường hay có xu hướng phức tạp hóa mọi flow vì sợ người dùng không hiểu, người dùng "ngu si", v.v. Cho nên người ta hay bỏ thời gian vào việc suy đoán và lựa chọn giùm đường đi cho người dùng, chặn cái này cái kia, thậm chí tìm cách bỏ bớt các thông báo cần thiết. Khi đã cho dùng thử và bắt đầu cảm thấy tự tin thì họ lại bắt đầu đơn giản hóa nhiều thứ. Tuy không có thẩm quyền can thiệp vào chuyện này,  lập trình viên nên sẵn sàng cho những thay đổi tới lui như vậy.)

Có một cách giúp phần mềm ổn định và logic là xây dựng một hệ thống tổng quát cho mọi tình huống, linh động, để người dùng muốn làm gì thì làm. Thay vì bắt người dùng hạnh phúc theo một cách nào đó, thì tốt nhất để họ tự "mưu cầu hạnh phúc". Chẳng hạn, chúng ta có custom fields hay custom flows trong các phần mềm như Jira, tùy chọn màu trong những phần mềm của Microsoft, add on trong các trình duyệt. Bên cạnh đó, undo (như trong Remember the milk) là một công cụ tuyệt vời để người dùng có thể yên tâm khám phá. Tất nhiên, nhìn ra những điều như vậy trong những ngày đầu xây dựng một phần mềm thực sự không dễ. Thường thì những ý tưởng khái quát sẽ nằm sau những giới hạn bí ẩn, không rõ lí do kiểu như người dùng chỉ được tạo một cái gì đó, liên kết một đối tượng kiểu này với đối tượng kiểu khác, không được nhập quá bao nhiêu đó thông tin. Cả người viết yêu cầu và lập trình viên phải nhìn ra các giới hạn ẩn trong yêu cầu của phần mềm để tìm kiếm những bản chất và khái niệm khái quát hơn đằng sau những yêu cầu đó.

(Tôi có nhớ đã đọc ở ít nhất một cuốn sách toán là khi không thể tìm lời giải cho một bài toán, hãy tìm lời giải cho bài toán tổng quát của nó. Nếu bạn vẫn nhớ những bài toán phổ thông thì sẽ thấy sự khái quát trước hết đến từ việc loại bỏ đi những giới hạn cụ thể, chẳng hạn từ (x+1)2 >= 4x ta có (x+y)2 >= 4xy, rồi (x+y)n >= (tôi quên rồi), hay (x1+…+xn)2 >= (tôi cũng quên rồi). Dĩ nhiên là đi xa hơn để nhìn ra giới hạn từ số n (lũy thừa n), hay phép cộng (+) thì phức tạp hơn từ 2 (bình phương), và cần những bộ óc siêu việt hơn.)

Cuối cùng, phải nhắc lại là UX là một thứ rất khó, và quyết định nhầm là chuyện hoàn toàn có thể xảy ra. Nhiệm vụ của lập trình viên là lập trình và lập trình thật tốt, vì vậy trong bất cứ tình huống nào, cũng cần đảm bảo phần mềm ở trong trạng thái vận hành được, và việc thay đổi yêu cầu chức năng, dù nhỏ hay lớn, đều có thể thực hiên trong một khoảng thời gian xác định được, và sẽ không làm mất đi khả năng vận hành của phần mềm.

Quản lí URI trong web application

Đây không phải là chuyện gì cao siêu quá về kĩ thuật, mà chỉ là có nghĩ tới hay không thôi. Khi làm việc với những MVC web framework thông thường như Spring hay Struts, nhất là khi web application sử dụng JavaScript và AJAX nhiều thì tập URI cần được quản lí và tổ chức một cách cẩn thận. ("MVC web framework thông thường" ở đây tôi dùng để chỉ các MVC framework mà bạn phải khai báo cụ thể Model, Controller, View và mapping giữa URI và Controller. Chú ý là những framework dạng component như JSF cũng có thể được xem là đi theo pattern MVC.)

Thông thường, URI được sử dụng ở phía client trong JavaScript hay view template dưới dạng string literal, có thể ghép nối một số parameter, kiểu như var url = 'delete/nodeId/' + nodeId. Vì URI chỉ là literal string, nên nói chung IDE sẽ không giúp gì nhiều cho chúng ta trong việc tìm kiếm hay báo lỗi. Khi đó chúng ta sẽ không dễ dàng tìm ra handler tương ứng với URI đó, và cũng khó tìm kiếm một cách nhanh chóng và chắc chắn những chỗ mà URI này được sử dụng. Ở dự án mà tôi đang tham gia, đôi khi tôi thấy những URI dạng /something/update/something/update2, chỉ vì lập trình viên không dám sửa handler có sẵn.  (Về lí thuyết, nếu mà cả phía client và server đều có unit test đầy đủ thì sửa URI cũng không quá rủi ro, nhưng không phải dự án nào cũng đủ tầm nhìn và tài nguyên để viết unit test tới mức độ này :D). Hệ quả là chúng ta sẽ có một tập URI lộn xộn, không theo một convention nào, và có thể trùng lặp về chức năng.

Về mặt người dùng, bạn có thoải mái không khi trả tiền và đưa dữ liệu lên một web application mà chỉ tập URI thôi đã lộn xộn rồi? (Tôi hỏi vậy thôi, chứ chắc là có, nhiều công ty dễ dãi một cách kì lạ khi đặt niềm tin vào mọi hệ thống mà họ sẽ thuê. Dù sao thì tôi chỉ là lập trình viên bình thường nên có khi chỉ nghĩ kiểu ếch ngồi đáy giếng.) Rõ ràng đây là một biểu hiện khó che giấu của một sự bất ổn trong quy trình phát triển, bất kể bạn được giới thiệu là họ áp dụng quy trình abcxyz gì đó.

Để quản lí được URI có lẽ không quá khó, nhưng quan trọng là phải làm sớm, nếu để muộn thì rủi ro và công sức bỏ ra sẽ nhiều hơn (hiện tại tôi đang ở tình cảnh "quá muộn"). Ý tưởng là chuyển tất cả URI ra một chỗ riêng, mỗi URI có một định danh riêng. Tất cả mọi tham chiếu tới một URI đều thông qua định danh thay vì literal string. (Nói thì phức tạp, tóm lại là có thể dùng constant string, enum, hay một cái gì đó tương tự cho các URI/URI template.) Như thế URI có thể được thay đổi mà không gây ảnh hưởng, hoặc chỉ gây ảnh hưởng ở mức kiểm soát được, tới những thành phần khác của hệ thống. Nếu trên URI chứa tham số, ví dụ như {id} trong testcase/{id}/update, thì URI cần được tạo ra từ URI template và các tham số, chứ không phải bằng cách xây dựng string thủ công như 'testcase/' + id + '/update'.

Và tất nhiên, cấu trúc có chặt chẽ đến đâu, unit test có phủ kín tới mấy thì việc quan trọng nhất vẫn là con người, cụ thể là lập trình viên. Cho nên dù thiết kế hay hiện thực có thế nào thì cũng phải nhớ nhắc nhở lập trình viên của bạn làm theo :D.

Đừng “hack”

(Bạn có biết máy tính của Lunar Module và Command Module của tàu Apollo chỉ có 36K 16-bit words ROM và 2K 16-bit words RAM.)

Gần đây tôi gặp phải một vấn đề với Dojo trên IE9. Có một chức năng cần phải refresh DataGrid nằm trong một Dialog mỗi khi Dialog đó xuất hiện, nhưng Dialog này lại trống trơn khi chạy trên IE9. Nguyên nhân là vì DataGrid của Dojo sẽ không render đúng khi bị ẩn đi, nên việc refresh DataGrid phải được thực hiện sau khi Dialog đã xuất hiện. Đây không phải là bug, chỉ là cách mà DataGrid hoạt động. Tuy nhiên, có người đã debug và bỏ bớt một đoạn code của DataGrid để nó không gây ra lỗi, mặc dù họ không biết tại sao đoạn mã đó gây lỗi, và nó dùng để làm gì, nên cũng không thể đảm bảo việc này sẽ không gây lỗi ở chỗ khác. Đây không phải là lần đâu tiên tôi bắt gặp một cách “hack” như vậy, và không may nó lại là một trong những hướng giải quyết vấn đề thường gặp. Khi chuyện nảy sinh, đầu tiên lập trình viên sẽ nhìn quanh xem có method hay API nào đó giúp giải quyết khó khăn. Bước hai, họ tìm kiếm nguyên nhân từ các công cụ. Chậm là do đường truyền, tại trình duyệt, lỗi thì do framework, v.v. Bước ba, “đoán” rằng IE, Spring, Dojo, hay một công cụ nào khác là nguyên nhân (công cụ làm sao mà nói được), họ tìm kiếm các phương pháp để “trick” trình duyệt hay framework để che lấp phần hiện tượng của vấn đề, và thế là xong.

Thật ra, phải nói rằng cách giải quyết đã nói ở trên rất thông minh và có thể cho hiệu quả tốt. Tuy nhiên, rắc rối không nằm ở chuyện dùng cách “hack” hay không, mà là ở quan điểm của lập trình viên với những giải pháp kiểu này. Cần nhớ là đây chỉ đơn giản là cách thức cuối cùng, khi không còn con đường nào khác. Có thể nó thể hiện sự thông minh, nhanh trí của một lập trình viên, nhưng hơn hết, nên xem nó như là dấu hiệu cho thấy việc lựa chọn framework không phù hợp, thiết kế chương trình không tối ưu cho platform, hay sự thiếu hiếu biết của lập trình viên về hệ thống v.v. Mặt khác, giải pháp kiểu này sẽ góp phần couple hệ thống vào cơ chế hoạt động bên trong của framework, một runtime hay một phiên bản nào đó của chúng—những thứ vốn có thể thay đổi bất cứ lúc nào. Do vậy, nếu không tổ chức mã nguồn gọn gàng (và theo kinh nghiệm cá nhân thì thường là không) những người đi sau kế thừa tất nhiên sẽ phải rất vất vả nếu cần nắm bắt ý tưởng “sáng tạo” của người đi trước.

Nếu bạn là một người cẩn thận, hãy cố gắng thuyết phục bản thân rằng lỗi là điều bản thân khó tránh khỏi khi viết code. Và khi vấn đề xảy ra, thay vì giành thời gian để chê bai một library hay framework nào đó, hãy dùng nó để xem lại phần code của mình, cách mình hiểu và sử dụng các library hay framework, cũng như cơ chế hoạt động của chúng, để tìm ra một giải pháp gọn đẹp và hiệu quả.

Business logic, application logic và domain logic

Nếu tìm định nghĩa domain logic trên Wikipedia, bạn sẽ được redirect tới business logic với dòng đầu tiên “Business logic, or domain logic, is a non-technical term…”. Trên thực tế, domain logic chỉ là một phần của business logic. Phân biệt rõ các khái niệm này rất có ích trong việc xây dựng và hiện thực user story cho enterprise application, đặc biệt là khi sử dụng pattern Domain Model.

Business logic = domain logic + application logic

Về cơ bản, domain logic dùng để chỉ các logic mang tính bản chất, luôn đúng cho một business domain, phản ánh tính chất của các domain object và mối quan hệ giữa chúng với nhau, mà không phụ thuộc vào một application. Trong khi đó, application logic là các logic riêng cho một application cụ thể. Các application khác nhau sẽ có các application logic khác nhau. Việc thay đổi application logic không nên dẫn đến sự thay đổi ở domain logic. Đặt trong ngữ cảnh của một enterprise application dạng client-server với nhiều loại client (web, desktop application, mobile app, v.v), domain logic là thành phần lõi ở server, được chia sẻ giữa các client, còn application logic quy định hành vi của từng client khi tương tác với người dùng.

Chúng ta sẽ xem xét một ví dụ để làm rõ các khái niệm. Trong việc quản lí dự án, ta quy định mối build phải thuộc một và chỉ một release nào đó. Khi đó, để mối quan hệ này luôn được đảm bảo, việc một release bị xóa sẽ dẫn đến các build thuộc về nó bị xóa theo. Thao tác này thuộc về domain logic. Khi được người dùng yêu cầu xóa một release, phần mềm quản lí dự án có thể đáp ứng đúng như domain logic, hoặc bắt buộc người dùng phải tự xóa các build trong release, hoặc có thể đòi hỏi sự xác nhận của quản lí dự án v.v. Những thao tác này phát sinh và thay đổi theo hoàn cảnh sử dụng, và không làm thay đổi ngữ nghĩa của build và release. Đây chính là application logic.

Việc xây dựng user story cho một hệ thống, vốn không liên quan gì đến lập trình, cũng được lợi từ việc phân tách application logic và business logic. Theo kinh nghiệm cá nhân, nó hạn chế sự trùng lặp khi diễn giải các bước trong user story, giúp lập trình viên và tester tra cứu dễ dàng hơn, và có những định hướng tốt hơn khi hiện thực. Ngoài ra, tách biệt các vấn đề (domain logic, workflow, user experience, v.v) sẽ giúp ngữ cảnh của những cuộc thảo luận cụ thể và rõ ràng, và tất nhiên sẽ hiệu quả hơn.

Cần chú ý là không có ranh giới tuyệt đối giữa domain logic và application logic. Việc sắp đặt như thế nào phụ thuộc vào cách nhìn về business domain, yêu cầu của application, v.v. Xét cho cùng, tất cả những việc phân chia này đều nhằm đáp ứng những nguyên tắc cơ bản khi thiết kế phần mềm.

Tham khảo:

  • Patterns of Enterprise Application Architecture, của Martin Fowler và các tác giả khác.
  • Architecting Microsoft .NET Solutions for the Enterprise, của Dino Esposito và Andrea Saltarello.
  • Pros and Cons of Data Transfer Objects, MSDN.