内容


IBM Bluemix 上移动应用开发三层架构原型

Comments

移动应用开发是 Bluemix 上非常重要的应用场景之一。为了满足各种移动应用开发的需求,我们提出了一种 Bluemix 上移动应用开发的三层架构,即移动终端应用层、应用服务器层和数据库层,结构灵活,实现简单,可扩展性强。基于此思路,我们开发了一个移动应用原型——BlueListDelta,以 Android 应用为客户端,Java Liberty Runtime 为应用服务器层,非关系型数据库 Cloudant 服务为数据库层,轻松实现云中数据访问和业务逻辑等多种功能。

开发时您需要

  • 一个 Bluemix 帐号
  • 熟悉 Java 应用开发
  • 熟悉 Android 开发环境
  • 熟悉 Cloudant 服务
  • 熟悉 CloudFoundry 命令行工具 - cf

移动应用开发的三层架构

MobileData 服务是 Bluemix 开发移动应用的重要组成部分。使用 MobileData 服务开发应用非常简单,Bluemix 提供了成熟的 SDK 用于多种移动平台(Android 和 iOS 等),客户端只要通过几行代码,就可以将移动应用的数据保存在到 Bluemix 云端,实现数据的增加、删除、修改和查询功能。

简单来说,MobileData 服务可以被视为两层架构,即移动终端应用层和数据服务层,移动终端应用直接和数据服务层交互,实现起来也比较简单。然而,MobileData 服务主要侧重于数据存取功能,不适合实现复杂的业务逻辑。为了解决这个问题,同时保持架构的灵活性,受到经典 J2EE 三层架构启发,我们提出了 Bluemix 上移动应用开发的三层架构,即移动终端应用层、应用服务器层和数据库层,具体如下图所示。

移动终端应用层,实现移动终端的应用界面,和应用服务器层直接交互。应用服务器层提供移动终端交互接口,基于 HTTP 实现类似 REST 服务的接口,采用 JSON 数据格式,传输方便,适合终端应用解析处理。应用服务器层同时也要负责和数据库层交互,实现数据库的存取功能。数据库层对于移动终端来说是透明的,移动终端的所有交互都需要通过应用服务器层的接口。此外,在应用服务器层上还可以根据需求实现相关的业务逻辑。应用服务器层和数据库层都可以在 Bluemix 平台上轻松实现。

开发人员可以选择自己所喜欢的技术平台根据需要实现三层架构,应用服务器层可以选用任意的运行时,例如 Java Liberty、Node.js 和 Ruby,或是自带的 BuildPack;数据库层可以是关系型数据库,也可以是非关系型数据库。

基于此架构,我们也实现了一个简单的 Android 移动应用——BlueListDelta 。您可能听说过BlueList,这是 Bluemix 上一个使用 MobileData 服务来存储、删除、更新和查询存储在云中数据的移动应用。BlueListDelta 以 BlueList 应用程序基础版本为蓝本,没有采用 MobileData 服务,根据三层架构思路进一步开发,不仅实现了和 BlueList 相类似的云中数据存取(即增删改查)功能,更增加了一个应用服务器层的简单业务逻辑功能——黑名单,应用不允许用户添加黑名单中所包含的数据。BlueListDelta 应用,终端层采用 Android 原生应用开发,应用服务器层用 Java Liberty 运行时实现,数据库层则采用非关系型数据库 Cloudant 服务。

让我们一起开始构建 BlueListDelta 应用的历程吧!

步骤 1:在 Bluemix 上创建 BlueListDelta 应用

在 Dashboard 上创建一个应用

  1. 使用 Bluemix 帐号登录 Bluemix 网站。
  2. 点击 Add An Application 转到 Catelog 页面。
  3. 在 Runtimes 选项下,点击 Liberty for Java。
  4. 在 App 输入框,输入应用的名字,在本例中,填写 md001.
  5. 点击 Create,等待应用生成。

增加 Cloudant 服务

  1. 点击 Dashboard 页面上创建的应用。
  2. 点击 Add A Service。
  3. 在 Data Management 选项下选择 Cloudant NoSQL DB 服务。
  4. 点击 Create。如果出现提示要重新启动应用,点击 OK。

完成之后,将会生成一个类似下图的 Bluemix 应用。

步骤 2:开发 BlueListDelta 服务器端应用

接下来我们查看服务器端应用源代码,看看具体是如何实现从 Cloudant 服务存取数据以及如何和移动客户端交互的,代码核心类请参考 com.ibm.mds.MobileServlet.java。

获取 Cloundant 服务相关信息

以下代码片段显示如何从 VCAP_SERVICES 获取 Cloudant 服务相关信息,包括 Cloudant 服务器地址、端口、用户名和密码等。

// VCAP_SERVICES is a system environment variable
		// Parse it to obtain the for NoSQL DB connection info
		String VCAP_SERVICES = System.getenv("VCAP_SERVICES");
		String serviceName = null;
		if (VCAP_SERVICES != null) {
			// parse the VCAP JSON structure
			JSONObject obj = JSONObject.parse(VCAP_SERVICES);
			String dbKey = null;
			Set<String> keys = obj.keySet();
			// Look for the VCAP key that holds the cloudant no sql db
			// information
			for (String eachkey : keys) {
				if (eachkey.contains("cloudantNoSQLDB")) {
					dbKey = eachkey;
					break;
				}
			}
			if (dbKey == null) {
				System.out.println("Could not find cloudantNoSQLDB key"
						+ " in VCAP_SERVICES env variable ");
				return null;
			}
			JSONArray list = (JSONArray) obj.get(dbKey);
			obj = (JSONObject) list.get(0);
			serviceName = (String) obj.get("name");
			System.out.println("Service Name - " + serviceName);
			obj = (JSONObject) obj.get("credentials");
			databaseHost = (String) obj.get("host");
			port = ((Long) obj.get("port")).intValue();
			user = (String) obj.get("username");
			password = (String) obj.get("password");
			// url is not being used
			// url = (String) obj.get("url");
		}

和 Cloundant 服务建立连接

通过 CouchDB 库函数建立和 Cloudant 服务连接,CouchDB 库开源,提供非常简单的编程接口存取 Cloudant 数据库,可以从 https://github.com/helun/Ektorp 下载。下面的代码片段显示如何用 CouchDB 库建立 Cloudant 服务连接,只有和 Cloudant 服务建立连接之后才能进行数据的存取,包括增删改查等操作。

        CouchDbInstance dbInstance = null;
		try {
			System.out.println("Look up CouchDbInstance ...");
			dbInstance = (CouchDbInstance) new InitialContext()
					.lookup("java:comp/env/couchdb/" + serviceName);
			System.out.println("Look up successful !");
		} catch (NamingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if (dbInstance == null) {
			System.out.println("Creating couch db instance...");
			httpClient = new StdHttpClient.Builder().host(host).port(port)
			 .username(username).password(password).enableSSL(true)
			 .relaxedSSLSettings(true).build();
			dbInstance = new StdCouchDbInstance(httpClient);
		}
		CouchDbConnector dbConnector = new StdCouchDbConnector(dbName,
				dbInstance);
		dbConnector.createDatabaseIfNotExists();

在 Cloudant 中增加一条数据

调用 CouchDB 库的 create 函数在 Cloudant 数据库中增加一条记录,代码片段如下。

        Map<String, Object> data = new HashMap<String, Object>();
		data.put("name", DEFAULT_KEY);
		long id = System.currentTimeMillis();
		data.put("_id", id + "");
		data.put("value", value);
		data.put("creation_date", new Date().toString());
		dbConnector.create(data);
		System.out.println("Create Successful...");

在 Cloudant 中删除一条数据

调用 CouchDB 库的 delete 函数在 Cloudant 数据库中删除一条记录,代码片段如下。

   // get the document object by providing doc id
		HashMap<String, Object> obj = dbConnector.get(HashMap.class, id);
		dbConnector.delete(obj);
		System.out.println("Delete Successful...");

在 Cloudant 中修改一条数据

调用 CouchDB 库的 update 函数在 Cloudant 数据库中删除一条记录,代码片段如下。

   // get the document object by providing doc id
		HashMap<String, Object> obj = dbConnector.get(HashMap.class, id);
		obj.put("value", value);
		dbConnector.update(obj);
		System.out.println("Update Successful...");

在 Cloudant 中查询所有数据

调用 CouchDB 库的 getAllDocIds 函数获取 Cloundant 数据库中所有记录的 ID,然后逐条获取每条数据的具体信息,代码片段如下。

// get all the document IDs present in database
			List<String> docIds = dbConnector.getAllDocIds();
			for (String docId : docIds) {
				// get the document object by providing doc id
				HashMap<String, Object> obj = 
                dbConnector.get(HashMap.class,docId);
				JSONObject jsonObject = new JSONObject();
				String name = (String) obj.get("name");
				if (name != null && name.equals(DEFAULT_KEY)) {
					jsonObject.put("id", obj.get("_id"));
					jsonObject.put("name", obj.get("name"));
					jsonObject.put("value", obj.get("value"));
					System.out.println("====> " + jsonObject);
					jsonArray.add(jsonObject);
				}
			}

实现简单的黑名单逻辑

在增加数据时,采用简单的黑名单逻辑,包含关键字 black 的数据提交时会报错,不能被保存到数据库,代码片段如下。

   // A very simple black list validation
		if (value.toLowerCase().contains("black")) {
			doResp(formartErrJsonMsg(RESP_ERR_IN_BLACK_LIST,
					RESP_TXT_IN_BLACK_LIST), resp);
			return;
		}

返回 JSON 格式的处理结果给移动客户端

移动客户端发送请求,服务器端处理完之后,将处理结构封装成 JSON 数据格式,然后返回给移动客户端,如果处理成功返回具体数据,失败则返回错误代码。

	private void doResp(String jsonMsg, HttpServletResponse resp)
			throws IOException {
		resp.setContentType("application/json");
		resp.setCharacterEncoding("UTF-8");
		resp.getWriter().write(jsonMsg);
		resp.setStatus(200);
	}

步骤 3:编译和部署 BlueListDelta 服务器端应用

  1. 下载 BlueListDelta 服务器端应用源代码
  2. 导入 Eclipse,编译并且导出为 WAR Archive 包,例如 mobiled.war。如果您不方便从源代码编译,也可以从 MyData/目录下找到已经编译完成的 mobiled.war。
  3. 使用 cf 工具部署到 Bluemix,其中<app_name>是创建的应用名称,在本例中是 md001。

    cf push <app_name> -p mobiled.war

    想了解更多 cf 工具使用信息,请参考https://www.ng.bluemix.net/docs/#cli/index.html

步骤 4:开发 BlueListDelta Android 客户端应用

源代码的 MobileDAndroid 目录下可以找到 Android 客户端的所有代码。BlueListDelta Android 应用基于 BlueList Android 应用程序基础版本,界面代码没有改动,采用 Android 异步 HTTP 库Asynchronous HTTP Library for Android和服务器端进行交互。Asynchronous HTTP Library for Android 库开源,为 Android 应用提供非常简单的函数进行 HTTP 的异步通讯,对 JSON 数据格式支持良好,可以从 https://github.com/loopj/android-async-http下载。

下面的代码片段显示如何利用 Asynchronous HTTP Library for Android 库发送请求到服务器端查询数据并且显示在界面的过程,可以在 com.ibm.bluelist.MainActivity.java 类找到完整实现。

   RequestParams param = new RequestParams();
		param.add("cmd", "LIST");
		HttpUtil.get(param, new JsonHttpResponseHandler() {
			@Override
			public void onSuccess(int statusCode, Header[] headers,
					JSONObject response) {
				Log.i("listItems", response.toString());
				try {
					String respCode = response.getString("respCode");
					if (respCode.equals(HttpUtil.RESP_SUCCESS)) {
                        JSONArray bodyArray = response.getJSONArray("body");
                        itemList.clear();
                        for (int i = 0; i < bodyArray.length(); i++) {
                            JSONObject obj = bodyArray.getJSONObject(i);
                            itemList.add(new Item(obj.getString("value"), 
                            obj.getString("id")));
						}
						sortItems(itemList);
						lvArrayAdapter.notifyDataSetChanged();
					}
				} catch (JSONException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		});

当增加记录时,如果服务器端返回错误,则在界面上显示错误信息,具体代码片段同样可以在 com.ibm.bluelist.MainActivity.java 类找到。

String respCode = response.getString("respCode");
if (respCode.equals(HttpUtil.RESP_SUCCESS)) {
	listItems();
	itemToAdd.setText("");
} else {
	Toast.makeText(getApplicationContext(),
			response.getString("respText"),
			Toast.LENGTH_LONG).show();
}

步骤 5:编译 BlueListDelta Android 客户端应用

  1. 搭建 Eclipse Android 开发环境
  2. 下载 BlueListDelta Android 客户端应用源代码
  3. 将客户端应用源代码导入 Eclipse Android 工程。
  4. 修改 com.ibm.bluelist.HttpUtil.java 文件,将地址指向之前创建应用的链接。
private final static String MOBILE_BACKEND_URL =
     "http://<app_name>.mybluemix.net/MobileServlet";
private final static String MOBILE_BACKEND_URL =
     "http://md001.mybluemix.net/MobileServlet";
  1. 保存并重新编译应用。

步骤 6:运行 BlueListDelta Android 客户端应用

  1. 在模拟器中运行 BlueListDelta 应用。(在 Eclipse 中选择工程,点击 Run > Run As > Android Application。)
  2. 可以随便增加/修改/删除一些水果条目。
  3. 添加水果 blackberry 失败,错误信息提示在黑名单中(所有包含 black 的条目都不能添加,这是在应用服务器层实现的黑名单逻辑)。

步骤 7:查看 Cloudant 服务中的数据

  1. 使用 Bluemix 帐号登录 Bluemix 网站。
  2. 在 Dashboard 页面中点击创建的应用程序。
  3. 点击 Cloudant NoSQL DB,点击 Launch 打开 Cloudant 服务管理界面。
  4. 逐条可以看到 Cloudant 服务中存储的数据条目,例如下图显示的就是 Cloudant 数据库中保存的 apple 这条记录。可以修改此 value 的值,修改的结果会体现在 BlueListDelta Android 客户端中,这是因为应用数据是保存在 Cloudant 云服务中的。

总结

在 Bluemix 上开发移动应用是一件非常简单便捷的事情,使用 MobileData 服务就可以方便地存取云中的数据,Bluemix 也提供了相关的多个移动平台库,通过不多代码就可以实现数据访问功能。如果移动应用除了能访问云中数据中的需求,还希望在服务器端实现业务逻辑,则建议参考我们提出的移动应用三层架构,将应用分成移动终端应用层、应用服务器层和数据库层。总的来说,三层架构相比较 MobileData 服务更加灵活,可扩展性强,方便实现移动应用的业务逻辑和数据存取等多种功能,开发人员可以根据需要选用适合的运行时和数据库服务,将三层架构思路应用到多种场景中。


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Cloud computing, Java technology
ArticleID=989075
ArticleTitle=IBM Bluemix 上移动应用开发三层架构原型
publish-date=11132014