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ệ.
27 trang |
Chia sẻ: lylyngoc | Lượt xem: 1581 | Lượt tải: 1
Bạn đang xem trước 20 trang tài liệu Phát triển Java 2.0: NoSQL, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
Phát triển Java 2.0: NoSQL
ô 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ệ.
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.
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.
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
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 get và
setProperty. 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
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 Query và PreparedQuery
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
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
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
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
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