Tải bản đầy đủ (.pdf) (39 trang)

Làm chủ Grails: Các dịch vụ Grails và bản đồ Google pps

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (231.11 KB, 39 trang )

Làm chủ Grails: Các dịch vụ Grails và bản đồ Google
Hòa trộn công nghệ bên ngoài vào trong một ứng dụng Grails
Scott Davis , Tổng Biên tập, AboutGroovy.com
Tóm tắt: Scott Davis cho bạn biết làm cách nào bạn có thể nhúng các bản đồ vào
một ứng dụng Grails sử dụng những dịch vụ Web và APIs sẵn có miễn phí trong
bộ cài đặt mới nhất này của Làm chủ Grails. Ông sử dụng ứng dụng mẫu lập kế
hoạch-chuyến đi từ những cài đặt trước và đưa nó tới mức tiếp theo bằng mã địa lí,
Các bản đồ Google, và những dịch vụ Grails.
Tôi đã xây dựng một ứng dụng lập kế hoạch-chuyến đi từ bài viết đầu tiên trong
loạt bài này. Giờ khung làm việc Người điều khiển-Khung nhìn-Mô hình (Model-
View-Controller (MVC)) cơ bản đó đang ở đây, ta sẵn sàng để hòa trộn với những
kĩ thuật bên ngoài. Cụ thể, ta sẽ thêm một bản đồ. Tôi có thể nói, "Tôi đang đặt
một chuyến đi từ Denver tới Raleigh, với những điểm dừng ở San Jose và Seattle
dọc đường đi," nhưng một bản đồ sẽ giúp mô tả chuyến đi tốt hơn. Bạn có thể biết
rằng Seattle và Raleigh ở những phía đối diện nhau của nước Mỹ, nhưng một bản
đồ giúp bạn hình dung khoảng cách giữa hai thành phố này.
Cho bạn một ý tưởng ban đầu về ứng dụng gì sẽ làm ở phần cuối của bài viết này,
hãy vào trang và nhập mã IATA DEN trong hộp tìm kiếm.
Bạn nên kết thúc ở Sân bay Quốc tế Denver, như được biểu diễn trong Hình 1.
(Biết thêm về những mã IATA, xem bài viết của tháng trước.)

Hình 1. Sân bay Denver, nhờ Các bản đồ của Google

Bên cạnh việc hiển thị các sân bay của Mỹ bạn tạo một bảng HTML, lập kế hoạch-
chuyến đi cũng sẽ vẽ được các sân bay trên một bản đồ. Tôi sẽ sử dụng API Các
bản đồ Google miễn phí trong bài viết này. Bạn có thể sử dụng API Các bản đồ
Yahoo! miễn phí hoặc bất kỳ cái nào khác (xem Tài nguyên). Một khi bạn hiểu
những điều cơ bản về bản đồ Web trực tuyến, bạn sẽ hiểu rằng các API khác nhau
có thể hoán đổi cho nhau một cách hợp lí. Trước khi bạn có thể ánh xạ một phần
của giải pháp, bạn cần hiểu làm thế nào để một chuỗi ba kí tự đơn giản như DEN
biến đổi thành một điểm trên bản đồ


Mã địa lí
Khi bạn nhập DEN vào Các bản đồ Google, ứng dụng thực hiện một phép biến đổi
nhỏ đằng sau. Bạn có thể nghĩ tới những địa phương về mặt địa chỉ đường phố
như Đường 123 Main, nhưng các bản đồ Google cần một điểm vĩ độ/kinh độ để
hiển thị nó trên bản đồ. Hơn nữa bắt buộc bạn cung cấp điểm vĩ độ/kinh độ của
mình, nó dịch các địa chỉ người dùng có thể đọc được vào những vĩ độ/kinh độ
thay cho bạn. Phép biến đổi này được gọi là mã địa lí (geocoding) (xem Tài
nguyên).
Về bài viết này
Grails là một khung làm việc phát triển Web hiện đại mà hòa trộn với các kỹ thuật
Java™ quen thuộc như Spring và Hibernate với các thực hành đương thời như quy
ước qua cấu hình. Ghi vào Groovy, Grails cho bạn sự tích hợp liền một mạch với
mã Java của bạn trong khi việc thêm một cách mềm dẻo và linh động của một
ngôn ngữ tập lệnh. Sau khi bạn học Grails, bạn sẽ không bao giờ nhìn việc phát
triển Web lại theo cách tương tự.
Một phép biến đổi tương tự xảy ra khi bạn lướt Web. Về kỹ thuật, cách duy nhất
để liên lạc với một máy chủ Web từ xa là địa chỉ IP của máy chủ cung cấp. May
thay, bạn không cần nhập địa chỉ IP của mình. Bạn nhập một URL thân thiện vào
trình duyệt Web của bạn, và nó thực hiện việc gọi máy chủ Hệ thống Tên Miền
(Domain Name System (DNS)). Máy chủ DNS đổi URL thành địa chỉ IP tương
ứng, và trình duyệt thực hiện kết nối HTTP tới máy chủ từ xa. Tất cả điều này là
trong suốt với người dùng. DNS thực hiện Web vô cùng dễ dàng để sử dụng.
Những trình sinh mã địa lí (geocoder) thực hiện việc tương tự cho các ứng dụng
bản đồ dựa trên Web.
Tìm kiếm Web nhanh trên trình sinh mã địa kí miễn phí mang lại một số khả năng
phù hợp với nhu cầu mã hóa địa lí của những người lập kế hoạch chuyến đi. Cả
Google và Yahoo! cung cấp các dịch vụ mã địa lý như một phần tiêu chuẩn của
các API của họ, nhưng với ứng dụng này, tôi sẽ sử dụng dịch vụ mã địa lý miễn
phí được cung cấp trên geonames.org (xem Tài nguyên). RESTful API của nó cho
phép tôi chỉ ra rằng tôi đang cung cấp một mã IATA thay vì một giới hạn tìm

kiếm-văn bản chung chung. Tôi không có gì chống lại các cư dân của Ord, Ned.,
nhưng tôi quan tâm nhất là Sân bay Quốc tế Chicago O'Hare.
Nhập URL
vào
trình duyệt Web của bạn. Bạn nên xem XML trả về được trình bày trong Ví dụ 1:

Ví dụ 1. XML trả về từ yêu cầu mã địa lý

<geonames style="FULL">
<totalResultsCount>1</totalResultsCount>
<geoname>
<name>Denver International Airport</name>
<lat>39.8583188</lat>
<lng>-104.6674674</lng>
<geonameId>5419401</geonameId>
<countryCode>US</countryCode>
<countryName>United States</countryName>
<fcl>S</fcl>
<fcode>AIRP</fcode>
<fclName>spot, building, farm</fclName>
<fcodeName>airport</fcodeName>
<population/>
<alternateNames>DEN,KDEN</alternateNames>
<elevation>1655</elevation>
<continentCode>NA</continentCode>
<adminCode1>CO</adminCode1>
<adminName1>Colorado</adminName1>
<adminCode2>031</adminCode2>
<adminName2>Denver County</adminName2>
<alternateName lang="iata">DEN</alternateName>

<alternateName lang="icao">KDEN</alternateName>
<timezone dstOffset="-6.0" gmtOffset="-7.0">America/Denver</timezone>
</geoname>
</geonames>

Tham số name_equals trong URL bạn nhập vào là mã IATA cho sân bay. Nó chỉ
là một phần của URL mà cần bị thay đổi cho mỗi truy vấn. fcode=airp chỉ ra rằng
mã đặc trưng bạn đang tìm kiếm là một sân bay. Tham số style — short, medium,
long, hoặc full — chỉ rõ tính đầy đủ của câu trả lời XML.
Giờ đây bạn có một trình sinh mã địa lý, bước tiếp theo là tích hợp nó vào ứng
dụng Grails của bạn. Để làm được như vậy, bạn cần một dịch vụ.


Những dịch vụ Grails
Bằng điểm này trong loạt bài Làm chủ Grails, bạn có một ý tưởng hay về các phân
lớp miền, các kiểm soát, và các Trang Máy chủ Groovy (GSP) tất cả làm việc với
nhau trong một kiểu tổ hợp như thế nào. Chúng làm cho các thao tác cơ bản
Tạo/Truy lục/Cập nhật/Xóa (Create/Retrieve/Update/Delete (CRUD)) dễ dàng trên
một kiểu dữ liệu đơn. Dịch vụ mã địa lý này vượt một chút ra ngoài phạm vi của
các phép biến đổi Ánh xạ Quan hệ Đối tượng Grails (Grails Object Relational
Mapping (GORM)) từ các bản ghi cơ sở dữ liệu quan hệ tới POGOs (các đối
tượng Groovy cũ đơn giản). Ngoài ra, dịch vụ sẽ có thể được sử dụng bởi nhiều
hơn một phương thức. Cả save và update sẽ cần mã địa lý mã IATA, như bạn sẽ
thấy ở một thời điểm. Grails cung cấp cho bạn một nơi để lưu trữ các phương thức
được sử dụng phổ biến mà vượt qua bất kỳ lớp miền đơn: các dịch vụ.
Để tạo một dịch vụ Grails, gõ grails create-service Geocoder ở dòng lệnh. Xem
grails-app/services/GeocoderService.groovy, được trình bày ở Ví dụ 2, trong một
bộ soạn thảo văn bản:

Ví dụ 2. Một dịch vụ Grails nhiều nhánh-ra


class GeocoderService {
boolean transactional = true
def serviceMethod() {

}
}

Trường transactional được quan tâm nếu bạn đang thực hiện các truy vấn cơ sở dữ
liệu phức tạp trong cùng một phương thức. Nó gói mọi thứ trong một giao dịch cơ
sở dữ liệu đơn mà trả về nếu bất kỳ truy vấn nào lỗi. Bởi vì trong ví dụ này bạn
đang thực hiện gọi một dịch vụ Web từ xa, bạn có thể thiết lập nó một cách an
toàn là false.
serviceMethod tên là một trình giữ chỗ mà có thể được thay đổi thành cái gì đó
hơn là mô tả. (Các dịch vụ có thể chứa nhiều phương thức như bạn muốn.) Trong
Ví dụ 3, tôi thay đổi tên thành geocodeAirport:

Ví dụ 3. Phương thức dịch vụ trình sinh mã địa lý geocodeAirport()

class GeocoderService {
boolean transactional = false

//
def geocodeAirport(String iata) {
def base = "
def qs = []
qs << "name_equals=" + URLEncoder.encode(iata)
qs << "fcode=airp"
qs << "style=full"
def url = new URL(base + qs.join("&"))

def connection = url.openConnection()

def result = [:]
if(connection.responseCode == 200){
def xml = connection.content.text
def geonames = new XmlSlurper().parseText(xml)
result.name = geonames.geoname.name as String
result.lat = geonames.geoname.lat as String
result.lng = geonames.geoname.lng as String
result.state = geonames.geoname.adminCode1 as String
result.country = geonames.geoname.countryCode as String
}
else{
log.error("GeocoderService.geocodeAirport FAILED")
log.error(url)
log.error(connection.responseCode)
log.error(connection.responseMessage)
}
return result
}
}

Phần đầu của phương thức geocodeAirport xây dựng URL và thực hiện kết nối.
Những phần tử chuỗi-truy vấn được tập hợp trong một ArrayList và sau đó nối với
nhau bởi dấu "và". Phần cuối của phương thức phân tích kết quả XML sử dụng
một XmlSlurper Groovy và lưu trữ các kết quả trong một bản đồ băm.
Các dịch vụ Groovy không thể truy cập một cách trực tiếp từ một URL. Nếu bạn
muốn kiểm tra phương thức dịch vụ mới này trong trình duyệt Web của bạn, thêm
một bao đóng đơn giản cho AirportController, như được trình bày trong Ví dụ 4:


Ví dụ 4. Cung cấp một URL cho một dịch vụ trong một bộ điều khiển.


import grails.converters.*

class AirportController {
def geocoderService
def scaffold = Airport

def geocode = {
def result = geocoderService.geocodeAirport(params.iata)
render result as JSON
}


}

Spring xen dịch vụ vào bộ điều khiển một cách tự động nếu bạn định nghĩa một
biến thành viên với tên trùng với dịch vụ. (Với bí quyết để làm việc này, bạn phải
thay đổi kí tự đầu tiên của tên dịch vụ từ chữ hoa thành chữ thường theo những
quy ước đặt tên biến của Java.)
Để kiểm tra dịch vụ, nhập URL
http://localhost:9090/trip/airport/geocode?iata=den trong trình duyệt Web của bạn.
Bạn nên xem kết quả được thể hiện trong Ví dụ 5:

Ví dụ 5. Các kết quả của yêu cầu trình sinh mã địa lý

{"name":"Denver International Airport",
"lat":"39.8583188",
"lng":"-104.6674674",

"state":"CO",
"country":"US"}

Bao đóng geocode trong AirportController là chỉ để cho bạn kiểm tra sự đúng đắn
dịch vụ. Bạn có thể gỡ bỏ nó đi, hoặc bạn có thể để nó lại cho các cuộc gọi Ajax
tương lai có thể có. Bước tiếp theo là hệ số lại cơ sở hạ tầng Airport để tận dụng
ưu điểm của dịch vụ mã địa lý mới này.


Hòa trộn trong dịch vụ
Để bắt đầu, thêm các trường mới lat và lng vào grails-app/domain/Airport.groovy,
như được trình bày trong Ví dụ 6:

Ví dụ 6. Thêm các trường lat và lng vào Airport POGO

class Airport{
static constraints = {
name()
iata(maxSize:3)
city()
state(maxSize:2)
country()
}

String name
String iata
String city
String state
String country = "US"
String lat

String lng

String toString(){
"${iata} - ${name}"
}
}

Gõ grails generate-views Airport ở dấu nhắc lệnh để tạo các tệp tin GSP. Chúng
được gắn liên kết động ở thời gian chạy tới điểm này, nhờ dòng def scaffold =
Airport trong AirportController.groovy. Vì tôi muốn thực hiện vài thay đổi cho
các khung nhìn, tôi cần mã hóa thủ công.
Khi tạo một Airport mới, tôi sẽ giới hạn các trường người dùng-có thể soạn thảo là
iata và city. Trường iata là cần thiết cho truy vấn mã địa lý làm việc. Tôi để city ở
đây bởi vì tôi muốn cung cấp thông tin đó cho bản thân. DEN thực sự là ở Denver,
nhưng ORD (Chicago O'Hare) thì ở Rosemont, Ill., và CVG (Cincinnati, sân bay
Ohio) là ở Florence, Ky. Để hai trường này trong create.gsp và xóa phần còn lại,
do đó bây giờ create.gsp giống như Ví dụ 7:

Ví dụ 7. Thay đổi create.gsp

<g:form action="save" method="post" >
<div class="dialog">
<table>
<tbody>
<tr class="prop">
<td valign="top" class="name"><label for="iata">Iata:</label></td>
<td valign="top"
class="value ${hasErrors(bean:airport,field:'iata','errors')}">
<input type="text"
maxlength="3"

id="iata"
name="iata"
value="${fieldValue(bean:airport,field:'iata')}"/>
</td>
</tr>
<tr class="prop">
<td valign="top" class="name"><label for="city">City:</label></td>
<td valign="top"
class="value ${hasErrors(bean:airport,field:'city','errors')}">
<input type="text"
id="city"
name="city"
value="${fieldValue(bean:airport,field:'city')}"/>
</td>
</tr>
</tbody>
</table>
</div>
<div class="buttons">
<span class="button"><input class="save" type="submit" value="Create"
/></span>
</div>
</g:form>

Hình 2 thể hiện biểu mẫu kết quả:

Hình 2. Tạo biểu mẫu Airport

Biểu mẫu này được đưa tới bao đóng save trong AirportController. Thêm đoạn mã
trong Ví dụ 8 vào bộ điều khiển để thực hiện gọi tới geocodeAirport trước khi

Airport mới được lưu lại:

Ví dụ 8. Thay đổi bao đóng save
def save = {
def results = geocoderService.geocodeAirport(params.iata)
def airport = new Airport(params + results)
if(!airport.hasErrors() && airport.save()) {
flash.message = "Airport ${airport.id} created"
redirect(action:show,id:airport.id)
}
else {
render(view:'create',model:[airport:airport])
}
}

Số lượng lớn phương thức là giống hệt những gì bạn thấy nếu bạn gõ grails
generate-controller Airport ở dấu nhắc lệnh. Sự khác biệt duy nhất với bao đóng
được sinh ra mặc định là hai dòng đầu tiên. Dòng thứ nhất có HashMap từ dịch vụ
trình sinh mã địa lý. Dòng thứ hai kết hợp results HashMap với params HashMap.
(Vâng, việc hòa nhập hai HashMaps trong Groovy đơn giản như việc thêm chúng
với nhau.)
Nếu lưu trữ cơ sở dữ liệu hoàn thành, bạn được gửi một lần nữa tới hành động chỉ
dẫn, không có thay đổi nào được yêu cầu cho show.gsp, như được trình bày trong
Hình 3:

Hình 3. Thể hiện biểu mẫu Airport

Để hỗ trợ việc soạn thảo một Airport, để lại các trường iata và city trong edit.gsp.
Bạn có thể sao chép và dán phần còn lại của các trường từ show.gsp để thực hiện
chúng chỉ đọc. (Hoặc nếu bạn đã đặt "sao chép và dán là hình thức thấp nhất của

lập trình hướng đối tượng" từ bài viết trước đó, bạn có thể giải nén các trường phổ
biến vào một mẫu riêng và hoàn trả nó trên cả show.gsp và edit.gsp.) Ví dụ 9 trình
bày thay đổi edit.gsp:

Ví dụ 9. Thay đổi edit.gsp


<g:form method="post" >
<input type="hidden" name="id" value="${airport?.id}" />
<div class="dialog">
<table>
<tbody>
<tr class="prop">
<td valign="top" class="name"><label for="iata">Iata:</label></td>
<td valign="top"
class="value ${hasErrors(bean:airport,field:'iata','errors')}">
<input type="text"
maxlength="3"
id="iata"
name="iata"
value="${fieldValue(bean:airport,field:'iata')}"/>
</td>
</tr>
<tr class="prop">
<td valign="top" class="name"><label for="city">City:</label></td>
<td valign="top"
class="value ${hasErrors(bean:airport,field:'city','errors')}">
<input type="text"
id="city"
name="city"

value="${fieldValue(bean:airport,field:'city')}"/>
</td>
</tr>
<tr class="prop">
<td valign="top" class="name">Name:</td>
<td valign="top" class="value">${airport.name}</td>
</tr>
<tr class="prop">
<td valign="top" class="name">State:</td>
<td valign="top" class="value">${airport.state}</td>
</tr>
<tr class="prop">
<td valign="top" class="name">Country:</td>
<td valign="top" class="value">${airport.country}</td>
</tr>
<tr class="prop">
<td valign="top" class="name">Lat:</td>
<td valign="top" class="value">${airport.lat}</td>
</tr>
<tr class="prop">
<td valign="top" class="name">Lng:</td>
<td valign="top" class="value">${airport.lng}</td>
</tr>
</tbody>
</table>
</div>
<div class="buttons">
<span class="button"><g:actionSubmit class="save" value="Update" /></span>

<span class="button">

<g:actionSubmit class="delete"
onclick="return confirm('Are you sure?');"
value="Delete" />
</span>
</div>
</g:form>


Biểu mẫu kết quả được trình bày ở Hình 4:

Hình 4. Biểu mẫu soạn thảo Airport

Nhấn chuột vào nút Update gửi các giá trị biểu mẫu tới bao đóng update. Thêm
dịch vụ gọi và bản đồ băm trộn vào mã mặc định, như được trình bày trong Ví dụ
10:

Ví dụ 10. Thay đổi bao đóng update

def update = {
def airport = Airport.get( params.id )
if(airport) {
def results = geocoderService.geocodeAirport(params.iata)
airport.properties = params + results
if(!airport.hasErrors() && airport.save()) {
flash.message = "Airport ${params.id} updated"
redirect(action:show,id:airport.id)
}
else {
render(view:'edit',model:[airport:airport])
}

}
else {
flash.message = "Airport not found with id ${params.id}"
redirect(action:edit,id:params.id)
}
}

×