Phát triển Java 2.0: NoSQL

Mô hình hóa dữ liệu Schemaless với Bigtable và Gaelyk của Groovy

Kho dữ liệu NoSQL cũng giống như Bigtable và CouchDB là đều chuyển lên trọng tâm trong thời đại Web 2.0 bởi vì chúng có thể giải quyết các vấn đề mở rộng trên một quy mô lớn. Google và Facebook là hai trong số những tên tuổi lớn đã sử dụng NoSQL, và kể cả chúng tôi nữa. Kho dữ liệu Schemaless về cơ bản khác với cơ sở dữ liệu quan hệ truyền thống, nhưng việc tận dụng chúng dễ dàng hơn bạn nghĩ, đặc biệt là nếu bạn bắt đầu với mô hình miền domain chứ không phải là một quan hệ.

Andrew Glover, Tác giả và Nhà phát triển, Beacon50

 Andrew GloverAndrew Glover là một nhà phát triển, tác giả, diễn giả và là người điều hành doanh nghiệp với niềm đam mê phát triển các hệ thống điều khiển hành vi, tích hợp liên tục và phát triển phần mềm dựa trên quy trình Agile. Ông đã sáng lập ra nền tảng phát triển điều khiển hành vi easyb và là đồng tác giả của 3 cuốn sách: Continuous Integration (Tích hợp liên tục), Groovy in Action (Thực hành ngôn ngữ Groovy), và Java Testing Patterns (Các mẫu kiểm thử Java). Bạn có thể theo dõi và tìm hiểu về ông qua trang blogTwitter.



24 05 2013

Đôi điều về loạt bài này

Bối cảnh phát triển Java đã thay đổi hoàn toàn kể từ khi công nghệ Java đã xuất hiện lần đầu tiên. Nhờ các khung công tác nguồn mở hoàn thiện và cơ sở hạ tầng triển khai cho thuê tin cậy mà ngày nay chúng ta có thể lắp ráp, thử nghiệm, chạy và duy trì các ứng dụng Java một cách nhanh chóng và không tốn kém. Trong loạt bài này, Andrew Glover khám phá một loạt các công nghệ và các công cụ làm cho hình mẫu phát triển Java mới này trở nên khả thi..

Cơ sở dữ liệu quan hệ đã thống trị lưu trữ dữ liệu hơn 30 năm, nhưng việc cơ sở dữ liệu schemaless (hay NoSQL) ngày càng phổ biến đã cho thấy rằng điều này đang dần thay đổi. Trong khi các hệ quản trị cơ sở dữ liệu (RDBMS) cung cấp một nền tảng vững chắc để lưu trữ dữ liệu trong kiến trúc client-server truyền thống, nhưng nó không dễ dàng (hay tốn ít chi phí) để mở rộng. Trong thời đại mà các ứng dụng web có thể co giãn được như Facebook, Twitter thì điều này quả thực là một hạn chế đáng tiếc.

Trong khi lựa chọn cơ sở dữ liệu quan hệ trước đó (bạn còn nhớ về cơ sở dữ liệu hướng đối tượng chứ?) đã thất bại trong việc giải quyết các vấn đề cấp bách, thì các cơ sở dữ liệu NoSQL như Bigtable của Google hay SimpleDB của Amazon đã xuất hiện như một câu trả lời cho nhu cầu co giãn cao của các ứng dụng Web. Về bản chất, NoSQL có thể là giải pháp cho các vấn đề khó khăn — một trong các quá trình tiến hóa của ứng dụng Web không hơn không kém, nó cũng giống như việc tiến hóa tất yếu lên Web 2.0 vậy.

Phát triển kỹ năng về chủ đề này

Nội dung này nằm trong "đường dẫn đến kiến thức (knowledge path)" để bổ sung các kỹ năng của bạn. Xem Sử dụng NoSQL và phân tích Big Data

Trong loạt bài Phát triển Java 2.0, tôi sẽ giúp bạn tìm hiểu mô hình hóa dữ liệu schemaless, đây là một rào cản khi tiếp cận NoSQL bởi vì nhiều nhà phát triển đã được đào tạo theo lối tư duy quan hệ. Bạn sẽ được học về mô hình miền domain (chứ không phải là mô hình quan hệ) là chìa khóa để đơn giản hóa phương pháp của bạn. Nếu bạn đang sử dụng Bigtable, cũng như ví dụ của tôi, thì bạn cũng có thể dùng thêm Gaelyk để hỗ trợ. Gaelyk là một Framework mở rộng cho Google App Engine.

NoSQL: Một lối tư duy mới?

Khi các nhà phát triển bàn về cơ sở dữ liệu không-quan-hệ hay NoSQL, thì điều đầu tiên mà họ thường nhắc đến là việc phải thay đổi lối tư duy. Theo tôi, thực ra điều đó còn tùy vào cách tiếp cận ban đầu của bạn về mô hình hóa dữ liệu. Nếu bạn đã quen với việc thiết kế ứng dụng theo cách mô hình hóa cấu trúc cơ sở dữ liệu trước (có nghĩa là bạn phải tìm ra các bảng và các mối quan hệ của chúng trước), sau đó việc mô hình hóa dữ liệu với kho lưu trữ schemaless như Bigtable sẽ yêu cầu bạn phải xem xét lại cách làm. Tuy nhiên, nếu bạn bắt đầu thiết kế ứng dụng với mô hình miền domain thì sẽ cảm thấy phù hợp hơn với cấu trúc schemaless của Bigtable.

Xây dựng để mở rộng

Cùng với những vấn đề mở rộng ứng dụng web, chúng ta đều có những giải pháp mới. Facebook không dựa trên cơ sở dữ liệu quan hệ cho các nhu cầu lưu trữ của nó, thay vào đó nó sử dụng cách lưu trữ key/value (khóa/giá trị) — thực ra là một HashMap với hiệu suất cao. Các giải pháp in-house như Cassandra cũng được sử dụng bởi Twitter và Digg và gần đây cũng được trao tặng cho quỹ Apache Software Foundation. Google là một ví dụ Web có khả năng tăng trưởng bùng nổ cần thiết để tìm kiếm lưu trữ dữ liệu không-quan-hệ — Bigtable là kết quả.

Kho dữ liệu không-quan-hệ không có các bảng hay khóa chính, hay thậm chí là các khóa ngoại (mặc dù cả hai loại khóa này đều hiện diện trong một dạng nới lỏng). Vì thế bạn sẽ thất vọng nếu cố gắng áp dụng mô hình hóa quan hệ như là nền tảng cho mô hình hóa dữ liệu trong cơ sở dữ liệu NoSQL. Bắt đầu từ một mô hình miền domain giúp đơn giản hóa nhiều thứ; trong thực tế, tôi đã thấy sự linh hoạt của cấu trúc schemaless dưới mô hình miền domain chính là khả năng làm mới.

Sự phức tạp của việc chuyển từ một mô hình dữ liệu quan hệ đến một mô hình dữ liệu schemaless phụ thuộc vào cách tiếp cận của bạn: đó là bạn bắt đầu từ một quan hệ hay một thiết kế dựa trên miền domain. Khi bạn di chuyển sang kho dữ liệu như CouchDB hoặc Bigtable, bạn sẽ thật sự loại bỏ được những hạn chế của nền tảng lâu đời như Hibernate (ít nhất là từ bây giờ). Mặt khác, hiệu ứng green-pasture sẽ giúp bạn tự xây dựng nó. Và trong bài này, bạn sẽ tìm hiểu sâu về kho dữ liệu schemaless.


Các thực thể và mối quan hệ

Một kho dữ liệu schemaless cung cấp cho bạn sự linh hoạt để thiết kế mô hình miền domain với các đối tượng đầu tiên (một thứ gì đó mới hơn, các framework như Grails tạo thuận lợi một cách tự động). Công việc của bạn là tiến về phía trước sau đó map miền domain của bạn với kho dữ liệu, điều này rất đễ dàng đối với Google App Engine.

Trong bài "Phát triển Java 2.0: Gaelyk cho Google App Engine," tôi đã giới thiệu về Gaelyk, một framework dựa trên Groovy giúp làm việc với kho dữ liệu của Google. Phần lớn bài viết tập trung vào việc thúc đẩy các đối tượng Google Entity. Ví dụ sau đây (trích từ bài viết đó) cho thấy cách các thực thể đối tượng làm việc trong Gaelyk.

Liệt kê 1. Gán đối tượng vào thực thể
def ticket = new Entity("ticket")
ticket.officer = params.officer
ticket.license = params.plate
ticket.issuseDate = offensedate
ticket.location = params.location
ticket.notes = params.notes
ticket.offense = params.offense

Thiết kế bởi đối tượng

Các mô hình đối tượng trong thiết kế cơ sở dữ liệu cho thấy trong khuôn khổ ứng dụng Web hiện đại như Grails và Ruby on Rails, nhấn mạnh việc thiết kế mô hình đối tượng và xử lý việc tạo giản đồ cơ sở dữ liệu cho bạn.

Phương pháp này phản đối các tiến trình dai dẳng, nhưng ta sẽ dễ dàng thấy nó trở nên tẻ nhạt nếu bạn sử dụng nhiếu thực thể vé — ví dụ, nếu bạn đã tạo ra (hay tìm kiếm) chúng trong các servlet khác nhau. Một servlet (hay Groovlet) xử lý các nhiệm vụ của bạn có thể loại bỏ đi một số gánh nặng. Một lựa chọn tự nhiên hơn — như tôi sẽ chứng minh — sẽ là một mô hình đối tượng Ticket.


Trở lại với Race (đường đua)

Thay vì tạo lại các ticket ví dụ từ phần giới thiệu Gaelyk, tôi sẽ giữ mọi thứ mới mẻ và sử dụng một chủ đề xuyên suốt bài này để xây dựng một ứng dụng chứng minh các kỹ thuật mà tôi đã thảo luận..

Sơ đồ nhiều-nhiều ở Hình 1 cho thấy, một Race có nhiều Runners và một Runner có thể thuộc về nhiều Races.

Figure 1. Race and runners
Sơ đồ mô tả mối quan hệ nhiều-nhiều giữa Races (các đường đua) và Runners (các vận động viên).

Nếu tôi được sử dụng một cấu trúc bảng quan hệ để thiết kế mối quan hệ này thì tôi cần ít nhất ba bảng: bảng thứ ba chính là bảng nối liên kết một mối quan hệ nhiều-nhiều. Tôi rất vui vì không bị ràng buộc với mô hình dữ liệu quan hệ. Thay vào đó, tôi sẽ sử dụng Gaelyk (và mã Groovy) để lập bản đồ quan hệ nhiều-nhiều này để trừu tượng Bigtable của Google cho Google App Engine. Thực tế là Gaelyk cho phép một Entity được đối xử như một Map để giúp quá mình trở nên đơn giản.

Mở rộng quy mô với Shards

Sharding là một dạng của phân vùng sao chép cấu trúc bảng qua các nút nhưng phân chia hợp lý dữ liệu của chúng. Ví dụ, một nút có thể có tất cả các dữ liệu liên quan đến tài khoản cư trú ở Mỹ và một nút khác cho tất cả các tài khoản cư trú ở châu Âu. Những thách thức của Shards xảy ra khi các nút có mối quan hệ — đó là, cross-shard joins. Đó là một vấn đề khó giải quyết và trong nhiều trường hợp không được hỗ trợ. (Xem mục Tài nguyên để thấy đường dẫn đến bài thảo luận của tôi với Google's Max Ross về sharding và thách thức khả năng mở rộng với cơ sở dữ liệu quan hệ.)

Một trong những nét đẹp của kho lưu trữ schemaless là tôi không cần phải biết những gì sắp xảy ra; điều đó có nghĩa tôi có thể thay đổi dễ dàng hơn với một lược đồ (schema) cơ sở dữ liệu quan hệ. (Lưu ý rằng tôi không ngụ ý bạn không thể thay đổi lược đồ, tôi chỉ nói rằng sự thay đổi đó sẽ được xem xét dễ dàng hơn.) Tôi sẽ không định nghĩa các thuộc tính của đối tượng miền domain — Tôi trì hoãn việc đó là để Groovy tự xử lý động (về bản chất, để làm các đối tượng của tôi ủy quyền cho các đối tượng Entity của Google). Thay vào đó, tôi sẽ dành nhiều thời gian để tìm ra cách mà tôi muốn tìm các đối tượng và xử lý các mối quan hệ. Đó là NoSQL và các framework khác nhau tận dụng kho dữ liệu schemaless chưa được tích hợp.

Mô hình lớp cơ sở

Tôi sẽ bắt đầu bằng việc tạo một lớp cơ sở chứa một thể hiện của một đối tượng Entity. Sau đó, tôi sẽ cho phép các lớp con có thuộc tính dynamic được thêm vào tương ứng với các thể hiện Entity thông qua phương thức setProperty. setProperty được gọi cho bất kỳ setter thuộc tính nào mà không thật sự tồn tại trong một đối tượng. (Nếu bạn thấy điều này có vẻ lạ thì đừng lo, bạn sẽ hiểu khi bắt tay vào thực hành.)

Liệt kê 2 là đoạn mã mở đầu trong ứng dụng của tôi tại thể hiện Model:

Liệt kê 2. Một lớp mô hình cơ sở đơn giản
package com.b50.nosql

import com.google.appengine.api.datastore.DatastoreServiceFactory
import com.google.appengine.api.datastore.Entity

abstract class Model {

 def entity
 static def datastore = DatastoreServiceFactory.datastoreService

 public Model(){
  super()
 }

 public Model(params){
  this.@entity = new Entity(this.getClass().simpleName)
  params.each{ key, val ->
   this.setProperty key, val
  }
 }

 def getProperty(String name) {
  if(name.equals("id")){
   return entity.key.id
  }else{
   return entity."${name}"
  }
 }

 void setProperty(String name, value) {
  entity."${name}" = value
 }

 def save(){
  this.entity.save()
 }	
}

Hãy lưu ý cách mà lớp trừu tượng định nghĩa một hàm khởi tạo lấy Map của các thuộc tính — Tôi hoàn toàn có thể thêm vào nhiều hàm khởi tạo sau. Thiết lập này khá tiện dụng cho các framework Web, thường sử dụng để đón các tham số được gửi từ form. Gaelyk và Grails đưa các thông số vào một đối tượng gọi là params. Hàm khởi tạo lặp lại trên Map này và gọi phương thức setProperty cho mỗi cặp key/value.

Hãy nhìn vào phương thức setProperty ra thấy rằng khóa (key) được thiết lập vào thuộc tính name của entity, trong khi đó giá trị (value) tương ứng chính là giá trị của entity's.

Các thủ thuật Groovy

Như tôi đã đề cập, tính động của Groovy cho phép tôi bắt giữ các lời gọi phương thức đến các thuộc tính không tồn tại thông qua các phương thức getsetProperty. Vì vậy, các lớp con của Model trong Liệt kê 2 không cần phải tự định nghĩa các thuộc tính — chúng đơn giản chỉ cần ủy thác các lời gọi thuộc tính đến đối tượng entity.

Mã trong Liệt kê 2 chỉ ra nhiều điểm giá trị của Groovy. Trước tiên, tôi có thể bỏ qua các phương thức truy xuất của một thuộc tính bằng cách thêm @ vào trước thuộc tính. Tôi phải làm điều này đối với các đối tượng entity trong hàm khởi tạo, hay có thể gọi phương thức setProperty. Gọi setProperty tại thời điểm này rõ ràng là sẽ phá vỡ mô hình, như biến entity trong phương thức setProperty có thể có giá trị null.

Thứ hai, lời gọi this.getClass().simpleName trong hàm khởi tạo sẽ thiết lập "loại" của entity— thuộc tính simpleName sẽ mang tên của một lớp con mà không có tên package phía trước (lưu ý rằng simpleName thực sự gọi đến phương thức getSimpleName, nhưng Groovy cho phép tôi truy cập vào thuộc tính mà không cần gọi phương thức JavaBeans-esque tương ứng.)

Cuối cùng, nếu gọi đến thuộc tính id (là key của đối tượng), thì phương thức getProperty đủ thông minh để yêu cầu key cho id của nó. Trong Google App Engine, các thuộc tính key của entities được tự động sinh ra.

Lớp con: Race

Bạn có thể định nghĩa dễ dàng một lớp con Race như Liệt kê 3:

Liệt kê 3. Lớp con Race
package com.b50.nosql

class Race extends Model {
 public Race(params){
  super(params)
 }
}

Khi một lớp con được khởi tạo với danh sách các tham số (có nghĩa là một Map chứa các cặp key/value) thì một entity tương ứng được tạo ra trong bộ nhớ. Để lưu giữ nó, tôi chỉ cần phương thức save.

Liệt kê 4. Tạo một thể hiện Race và lưu nó vào kho dữ liệu GAE
import com.b50.nosql.Runner

def iparams = [:]
                              
def formatter = new SimpleDateFormat("MM/dd/yyyy")
def rdate = formatter.parse("04/17/2010")
              
iparams["name"] = "Charlottesville Marathon"
iparams["date"] = rdate
iparams["distance"] = 26.2 as double

def race = new Race(iparams)
race.save()

Trong Liệt kê 4 chính là Groovlet, một Map (được gọi là iparams) được tạo ra với ba thuộc tính — name (tên), date (ngày tháng), và distance (chiều dài) của race (đường đua). (Lưu ý rằng trong Groovy, một Map rỗng được tạo bằng cú pháp [:].) Vậy là một thể hiện mới của Race đã được tạo và lưu vào kho dữ liệu thông qua phương thức save.

Tôi có thể kiểm tra lại kho lưu trữ thông qua giao diện điều khiển Google App Engine để đảm bảo rằng dữ liệu của tôi đang ở đó, như trong Hình 2:

Figure 2. Viewing the newly created Race
Xem Race (đường đua) được tạo mới trong màn hình Google App Engine.

Các phương thức tìm kiếm dành cho các Entities đã tồn tại

Bây giờ tôi đã có một Entity, thật hữu ích khi tôi có thể truy lại nó; sau đó, có thể thêm vào một phương thức "finder". Trong trường hợp này, tôi sẽ tạo nó như một phương thức trong lớp (dạng static) và cho phép Races có thể được tìm kiếm theo tên (tức là tôi tìm kiếm dựa vào thuộc tính name). Tôi hoàn toàn có thể thêm vào các phương thức tìm kiếm đối với các thuộc tính khác.

Tôi cũng quy ước rằng nếu tên phương thức nào không có từ all thì xem như nó chỉ tìm một thể hiện. Nếu phương thức nào có từ all (như phương thức findAllByName) thì nó có thể trả về một Collection, hay List, hay nhiều thể hiện. Liệt kê 5 cho ta thấy phương thức tìm kiếm findByName:

Liệt kê 5. Một phương thức tìm kiếm đơn giản theo tên của Entity
static def findByName(name){
 def query = new Query(Race.class.simpleName)
 query.addFilter("name", Query.FilterOperator.EQUAL, name)
 def preparedQuery = this.datastore.prepare(query)
 if(preparedQuery.countEntities() > 1){
  return new Race(preparedQuery.asList(withLimit(1))[0])
 }else{
  return new Race(preparedQuery.asSingleEntity())
 }
}

Phương thức tìm kiếm đơn giản này sử dụng các truy vấn QueryPreparedQuery của Google App Engine để tìm các entity có loại là "Race," có tên giống (hay chính xác) với từ khóa nhập vào. Nếu tìm ra nhiều Race thỏa điều kiện này, phương thức sẽ trả về giá trị đầu tiên trong danh sách, theo thông số giới hạn trang là 1 (withLimit(1)).

Phương thức findAllByName cũng tương tự vậy nhưng sẽ có thêm 1 tham số với ý nghĩa rằng bạn muốn hiển thị bao nhiêu kết quả?, như trong Liệt kê 6:

Liệt kê 6. Tìm tất cả name (tên)
static def findAllByName(name, pagination=10){
 def query = new Query(Race.class.getSimpleName())
 query.addFilter("name", Query.FilterOperator.EQUAL, name)
 def preparedQuery = this.datastore.prepare(query)
 def entities = preparedQuery.asList(withLimit(pagination as int))
 return entities.collect { new Race(it as Entity) }
}

Cũng giống như các phương thức tìm kiếm đã được định nghĩa, phương thức findAllByName tìm kiếm các thể hiện Race theo tên, nhưng nó trả về tất cảRaces. Phương thức collect (tập hợp) của Groovy khá hấp dẫn, bằng cách này: nó cho phép tôi thả vào vòng lặp tương ứng tạo ra các thể hiện Race. Lưu ý, Groovy cũng cho phép giá trị mặc định cho tham số của phương thức; do đó, nếu tôi không nhập vào giá trị thứ hai thì pagination sẽ có giá trị mặc định là 10.

Liệt kê 7. Tiến hành tìm kiếm
def nrace = Race.findByName("Charlottesville Marathon")
assert nrace.distance == 26.2

def races = Race.findAllByName("Charlottesville Marathon")
assert races.class == ArrayList.class

Các phương thức tìm kiếm ở Liệt kê 7 hoạt động như mong đợi: phương thức findByName trả về một thể hiện trong khi phương thức findAllByName trả về một tập hợp (giả sử có nhiều hơn "Charlottesville Marathon").

Đối tượng Runner (vận động viên) cũng không có nhiều khác biệt

Bây giờ tôi có thể thoải mái tạo và tìm kiếm các thể hiện của Race, tôi tạo một đối tượng Runner. Quá trình tạo cũng giống như khi tạo thể hiện Race; và tôi chỉ cần mở rộng Model, như trong Liệt kê 8:

Liệt kê 8. Dễ dàng tạo một Runner
package com.b50.nosql

class Runner extends Model{
 public Runner(params){
  super(params)
 }
}

Nhìn vào Liệt kê 8, tôi có cảm giác rằng chúng ta gần tới đích rồi. Tôi còn có thể tạo ra mối liên kết giữa các runners và races. Và tất nhiên, tôi sẽ mô hình hóa mối quan hệ của chúng là nhiều-nhiều bởi vì các vận động viên (runners) có thể chạy trên nhiều đường đua (races).


Mô hình hóa miền domain không cần lược đồ

Sự trừu tượng của Google App Engine trên Bigtable không phải là một mô hình hướng đối tượng; nghĩa là tôi không thể lưu lại các mối quan hệ nhưng tôi có thể chia sẻ các key (khóa). Do đó, để mô hình hóa mối quan hệ giữa Races và Runners, tôi sẽ lưu trữ một danh sách các key Runner bên trong mỗi thể hiện Race, và ngược lại.

Tôi sẽ phải nói thêm một chút logic, tuy nhiên, tôi muốn kết quả API thật tự nhiên — nên tôi không muốn yêu cầu một Race cho một danh sách key Runner, mà chỉ yêu cầu một danh sách Runners mà thôi. Cũng may là việc này không khó.

Trong Liệt kê 9, tôi đã thêm vào thể hiện Race hai phương thức. Khi một đối tượng Runner được gửi vào phương thức addRunner, thì id tương ứng của nó được thêm vào danh sách Collection những thuộc tính ids của các runners trong entity (thực thể). Nếu tồn tại một danh sách collection các runners, key của đối tượng Runner mới sẽ được thêm vào danh sách đó; nếu không thì một danh sách Collection mới sẽ được tạo và key của Runner (chính là thuộc tính id trong entity) cũng được thêm vào danh sách.

Liệt kê 9. Thêm vào và gọi ra các runners
def addRunner(runner){
 if(this.@entity.runners){
  this.@entity.runners << runner.id
 }else{
  this.@entity.runners = [runner.id]
 }
}

def getRunners(){
 return this.@entity.runners.collect {
  new Runner( this.getEntity(Runner.class.simpleName, it) )
 }
}

Khi phương thức getRunners trong Liệt kê 9 được gọi, một danh sách các đối tượng Runner được tạo từ danh sách ids. Do đó, một phương thức mới (getEntity) được định nghĩa trong lớp Model, như trong Liệt kê 10:

Liệt kê 10. Tạo một entity từ một id
def getEntity(entityType, id){
 def key = KeyFactory.createKey(entityType, id)			
 return this.@datastore.get(key)
}

Phương thức getEntity sử dụng lớp KeyFactory của Google để tạo key cơ bản phục vụ cho việc tìm kiếm các entity riêng lẻ bên trong kho dữ liệu.

Cuối cùng, một hàm khởi tạo mới được định nghĩa để tiếp nhận một loại entity, như trong Liệt kê 11:

Liệt kê 11. Một hàm khởi tạo mới được thêm vào
public Model(Entity entity){
 this.@entity = entity
}

Như bạn thấy trong các Liệt kê 9, 10, và 11, và mô hình đối tượng của Hình 1, tôi có thể thêm một Runner vào bất kỳ Race nào, và tôi cũng có thể lấy danh sách các đối tượng Runner từ bất kỳ Race nào. Trong Liệt kê 12, tôi tạo ra một liên kết tương tự bên phía Runner của biểu thức. Liệt kê 12 hiển thị các phương thức của lớp Runner.

Liệt kê 12. Các Runners và Races của chúng
def addRace(race){
 if(this.@entity.races){
  this.@entity.races << race.id
 }else{
  this.@entity.races = [race.id]
 }
}

def getRaces(){
 return this.@entity.races.collect {
  new Race( this.getEntity(Race.class.simpleName, it) )
 }
}

Bằng cách này, tôi đã quản lý mô hình hai đối tượng miền domain với một kho dữ liệu schemaless.

Kết thúc race với các runners

Bây giờ những gì tôi cần phải làm là tạo ra một đối tượng Runner và thêm nó vào một Race. Nếu tôi muốn mối quan hệ là hai chiều, giống như mô hình đối tượng mà tôi đã chỉ ra ở Hình 1, và tôi cũng có thể thêm đối tượng Race vào Runner, như thể hiện ở Liệt kê 13:

Liệt kê 13. Các Runners với Races của chúng
def runner = new Runner([fname:"Chris", lname:"Smith", date:34])
runner.save()

race.addRunner(runner)
race.save()

runner.addRace(race)
runner.save()

Sau khi thêm mới một Runner vào race và hàm save của Race, thì kho dữ liệu được cập nhật với một danh sách các ID như thể hiện ở Hình 3:

Hình 3. Xem thuộc tính mới của các Runners trong một Race
Xem các thuộc tính mới của runners (các vận động viên) trong đường đua.

Bằng cách kiểm tra chặt chẽ các dữ liệu trong Google App Engine, bây giờ bạn có thể thấy một thực thể Race có một danh sách các Runners, như được hiển thị ở Hình 4.

Hình 4. Xem danh sách các Runners mới
Xem danh sách các vận động viên mới.

Tương tự, trước khi thêm một Race vào một đối tượng Runner mới thì thuộc tính không tồn tại, như thể hiện ở Hình 5.

Hình 5. Một Runner không có Race
Vận động viên không ở trên đường đua

Tuy nhiên, sau khi liên kết một Race với một Runner, kho dữ liệu sẽ bổ sung một danh sách mới các id của race.

Hình 6. Một Runner ra khỏi Race
Vận động viên ra khỏi đường đua.

Sự linh hoạt của kho dữ liệu schemaless là khả năng làm mới — các thuộc tính sẽ tự động được thêm vào nơi lưu trữ theo yêu cầu. Là một nhà phát triển, tôi không có nhu cầu cập nhật hay thay đổi giản đồ schema, càng không có nhu cầu triển khai nó!


Ưu và nhược điểm của NoSQL

Tất nhiên mô hình hóa dữ liệu schemaless cũng có cả ưu và nhược điểm. Một lợi thế của ứng dụng Back to the Races (Trở lại đường đua) là nó khá linh hoạt. Nếu tôi quyết định thêm một thuộc tính mới vào một Runner (như SSN), thì tôi không cần phải làm gì nhiều cả — mà thực tế, nếu tôi thêm nó vào các tham số của hàm khởi tạo thì nó sẽ ở đó. Điều gì sẽ xảy ra với các đối tượng cũ mà không được tạo ra với SSN? Chẳng sao cả. Chúng nó một trường giá trị là null.

Tốc độ đọc

Tốc độ là một yếu tố quan trọng khi so sánh NoSQL với mô hình quan hệ truyền thống. Đối với một trang web hiện đại cung cấp dữ liệu cho hàng triệu người dùng (như Facebook chẳng hạn) thì mô hình quan hệ sẽ xử lý rất chậm, chưa kể nó còn tốn chi phí nữa. Ngược lại, kho dữ liệu NoSQL được xử lý rất nhanh khi đọc.

Mặt khác, tôi đã mô tả tính nhất quán và nguyên vẹn của hiệu năng một cách rõ ràng. Kiến trúc dữ liệu hiện tại trong ứng dụng đã cho tôi cái nhìn không giới hạn — về mặt lý thuyết, tôi có thể tạo ra vô số các cá thể của cùng một đối tượng. Khi xử lý key trong Google App Engine, chúng có các key độc lập, nhưng những thứ khác có thể sẽ giống nhau. Hơn nữa, không tồn tại tầng delete, nên nếu tôi sử dụng kỹ thuật tương tự để mô hình hóa quan hệ một-nhiều, và nếu xóa bỏ thành phần cha, tôi có thể kết thúc với thành phần con. Dĩ nhiên tôi có thể thực hiện việc kiểm tra tính toàn vẹn — quan trọng là: tôi phải tự mình làm điều đó (giống như đã làm với những thứ khác).

Việc sử dụng kho dữ liệu schemaless đòi hỏi phải tuân thủ nguyên tắc. Nếu tôi tạo ra các loại Races khác nhau — một số có tên, số thì không, một số có thuộc tính date, số khác thì có thuộc tính race_date— thì tôi chỉ cần phát triển phần gốc (và những người khác sẽ tiếp tục phát triển từ mã của tôi).

Tất nhiên ta vẫn có thể sử dụng JDO và JPA với Google App Engine. Tôi đã từng sử dụng cả hai mô hình quan hệ và schemaless trên nhiều dự án, nên tôi có thể nói rằng ở múc độ thấp thì Gaelyk API là linh hoạt và thú vị nhất. Một lợi ích khác của việc dùng Gaelyk là có được sự hiểu biết kỹ hơn về kho dữ liệu Bigtable và schemaless.


Kết luận

Thường thì các mốt nhất thời sẽ dễ bị lãng quên. Nhưng với NoSQL thì nó không giống vậy, nó giống với các nền tảng mới nổi để phục vụ cho việc phát triển các ứng dụng Web có khả năng co giãn linh hoạt. Cơ sở dữ liệu NoSQL sẽ không thay thế các hệ quản trị cơ sở dữ liệu (RDBMS) mà nó chỉ bổ trợ cho chúng. Vô số các công cụ và framework thành công dựa trên cơ sở dữ liệu quan hệ, và các RDBMS tự nó không có gì là nguy hiểm.

Cuối cùng, những gì mà cơ sở dữ liệu NoSQL làm là mở ra một giải pháp thay thế cho các mô hình dữ liệu quan-hệ-đối-tượng. Chúng cho thấy rằng những phương pháp đó là khả thi, và — dùng cho những trường hợp cụ thể — tốt hơn. Cơ sở dữ liệu schemaless rất phù hợp với các ứng dụng Web multinode cần truy xuất dữ liệu nhanh chóng và có khả năng mở rộng. Ngoài ra còn có thêm một lợi ích là chúng đang dạy cho các lập trình viên tiếp cận với mô hình hóa dữ liệu từ một quan điểm hướng-miền-domain hơn là quan điểm quan hệ.

Tài nguyên

Học tập

Lấy sản phẩm và công nghệ

  • Gaelyk: Bắt đầu với Groovy, framework phát triển ứng dụng nhẹ nhất cho Google App Engine.

Thảo luận

Bình luận

developerWorks: Đăng nhập

Các trường được đánh dấu hoa thị là bắt buộc (*).


Bạn cần một ID của IBM?
Bạn quên định danh?


Bạn quên mật khẩu?
Đổi mật khẩu

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Ở lần bạn đăng nhập đầu tiên vào trang developerWorks, một hồ sơ cá nhân của bạn được tạo ra. Thông tin trong bản hồ sơ này (tên bạn, nước/vùng lãnh thổ, và tên cơ quan) sẽ được trưng ra cho mọi người và sẽ đi cùng các nội dung mà bạn đăng, trừ khi bạn chọn việc ẩn tên cơ quan của bạn. Bạn có thể cập nhật tài khoản trên trang IBM bất cứ khi nào.

Thông tin gửi đi được đảm bảo an toàn.

Chọn tên hiển thị của bạn



Lần đầu tiên bạn đăng nhập vào trang developerWorks, một bản trích ngang được tạo ra cho bạn, bạn cần phải chọn một tên để hiển thị. Tên hiển thị của bạn sẽ đi kèm theo các nội dung mà bạn đăng tải trên developerWorks.

Tên hiển thị cần có từ 3 đến 30 ký tự. Tên xuất hiện của bạn phải là duy nhất trên trang Cộng đồng developerWorks và vì lí do an ninh nó không phải là địa chỉ email của bạn.

Các trường được đánh dấu hoa thị là bắt buộc (*).

(Tên hiển thị cần có từ 3 đến 30 ký tự)

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Thông tin gửi đi được đảm bảo an toàn.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=70
Zone=Công nghệ Java, Nguồn mở
ArticleID=931269
ArticleTitle=Phát triển Java 2.0: NoSQL
publish-date=05242013