Blur 上手 - 建于Hadoop 和 Lucene上的搜索工具
Getting Started with "Blur" - Search on Top of Hadoop and Lucene.
Blur是一个新的Apache 2.0许可的软件项目,提供了建于Hadoop和Lucene之上一个搜索功能。elasticsearch和Solr已经存在,为什么建立新的东西?虽然这些项目运作良好,不过他们没有与一个坚实的Hadoop生态系统集成。Blur始建专门针对大数据,从一开始考虑到可扩展性,冗余和性能,同时利用Hadoop堆栈中已经存在的所有优势。
一年半前,我的项目开始使用Hadoop的数据处理。很早,我们有网络问题,这使我们的HDFS集群网络连接充其量参差不齐。在一个周末,我们逐步失去了在集群的数据节点90中的47个网络连接。当我们在星期一早上的时候,我注意到,MapReduce系统是有点呆滞,但仍在工作。当我检查HDFS中,我看到我们的能力下降了约50%。集群上运行的fsck后,我惊奇地发现,上周末灾难性的失败但似乎文件系统仍然健康。这方面的经验,给我留下了深刻的印象。就在那时,我有个想法,以某种方式利用冗余和HDFS的容错来建立搜索系统的下一个版本,我才刚刚开始(重新)写。
我已经写了一个已在生产系统中几年的分片式Lucene的服务器。 Lucene的工作非常出色,做了一切我们需要的搜索工作。我们面临的问题是,它是运行在大铁箱是不是多余的,并不能很容易地扩展。看到Hadoop的一流的的可伸缩特征后,我决定寻找结合已经成熟和令人印象深刻的Lucene的功能设置与内置在Hadoop平台的可扩展性和冗余。因此这个实验项目Blur被创建。
Blur解决的最大的技术问题/功能:
- 整个数据集的快速大规模索引
- 自动分片Server故障转移
- 通过Lucene的NRT实现近实时更新的兼容性
- Lucene的FDT的文件压缩,同时保持随机存取性能
- Lucene的的WAL(预写日志)提供数据的可靠性
- Lucene直接R/W到HDFS中(seek写的问题)
- Lucene的目录缓存块的随机存取性能
数据模型
在Blur的数据存储在包含行的表中。行必须有一个唯一的行ID,并包含一个或多个记录。记录有一个独特的记录ID(行内唯一)和逻辑上弥补了单个记录的列进行分组的列家族。列包含一个名称和一个值,一个记录可以包含多个列具有相同的名称。
架构
Blur使用Hadoop的MapReduce框架索引数据,Hadoop的HDFS文件系统用于存储索引。Thrift 用于所有的进程间通信同时 Zookeeper 被用于了解系统状态和存储元数据。Blur的架构是由两种类型的服务器进程:
- Blur控制服务器
- Blur分片服务器
分片服务器,从所有当前在线的表提供0个或多个碎片服务。在每个碎片服务器中哪些碎片在线是通过在Zookeeper的状态信息来计算的。如果碎片服务器宕机,通过与Zookeeper的余下的碎片服务器交互检测到故障,并确定他们的丢失的碎片需要从HDFS得到服务。
控制服务器提供了集群单一的入口点(逻辑),撒出查询,收集回复,并提供一个单一的响应。控制器和分片服务器暴露相同的 Thrift API,这有助于方便调试。它还允许开发人员启动一个单一的分片服务器,并与它进行交互,以与大型集群同样的方式。许多控制服务器可以(并且应该)冗余运行。控制器作为网关承担服务于分片服务器所有数据。
更新/加载数据
目前有两种方法来加载和更新数据。首先是通过MapReduce的大量载入,第二个是用Thrift通过突变调用。
大量载入 MapReduce的范例
01.public class BlurMapReduce {Data Mutation Thrift Example
搜索数据
任何Blur数据模型中的元素是通过正常的Lucene的语义检索:analyzers。Analyzers 定义在Blur表中。
标准Lucene查询语法是搜索 Blur 默认的方式。如果标准语法以外的任何需要,可以直接用Java对象创建一个Lucene的查询,并通过专家查询API提交他们。
行内的列家庭分组允许跨列家庭类似什么,你会得到一个内部联接两个表之间共享相同的键(或在rowid)。对于有多个列家族复杂的数据模型,这使得有一个非常强大的搜索能力。
下面的示例搜索“value”作为一个完整的全文检索。如果我想在列家族"famB"中的单个字段"colA"中搜索“value”,查询应该类似“famB.colA:value”。
读取数据
取数可以通过按行或按记录。通过指定的ROWID或recordid创建一个选择的对象,指定列家庭或你想返回的列。如果没有指定,返回整个行或记录。
现状
Blur正接近它的第一个版本0.1,是相对稳定的。首次发布的候选应是在未来几周内可供下载。在此期间,可以在github上检查出来:
https://github.com/nearinfinity/blur
如何中断正在执行IO的 Quartz 作业
Interrupt a Quartz job that doing IO
如果你想中断正在执行IO的 Quartz 作业,在你使用 InterruptibleChannel 时这是可行的。引用一下Oracle链接:实现了这个接口的通道,是可中断的:如果一个线程在一个中断通道阻塞I/O操作,另一个线程能调用阻塞的线程的中断方法。这将导致的通道被关闭,被阻塞的线程收到一个ClosedByInterruptException,设置被阻塞的线程的中断状态。因此,获得自己工作的执行线程的作业计划,能保存供以后使用。当Quartz调度中断作业,你可以再调用该线程的interrupt()方法来停止读/写操作。这里有一个简单的例子:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
package demo;
// import statements excluded for brevity
public class MyJob implements InterruptableJob {
private static Logger LOG = LoggerFactory.getLogger(MyJob.class);
private volatile boolean isJobInterrupted = false;
private JobKey jobKey = null;
private volatile Thread thisThread;
public MyJob() {
}
public void execute(JobExecutionContext context) throws JobExecutionException {
thisThread = Thread.currentThread();
LOG.info("Thread name of the current job: " + thisThread.getName());
jobKey = context.getJobDetail().getKey();
LOG.info("Job " + jobKey + " executing at " + new Date());
try {
String fileUrl = "http://d2zwv9pap9ylyd.cloudfront.net/terracotta-3.6.1.tar.gz"; // 59 MB
String localFile = "terracotta-3.6.1.tar.gz";
download(fileUrl, localFile);
} catch (ClosedByInterruptException e) {
LOG.info("Caught ClosedByInterruptException... exiting job.");
} catch (IOException e) {
LOG.info("Caught IOException... exiting job.", e);
} finally {
if (isJobInterrupted) {
LOG.info("Job " + jobKey + " did not complete");
} else {
LOG.info("Job " + jobKey + " completed at " + new Date());
}
}
}
// this method is called by the scheduler
public void interrupt() throws UnableToInterruptJobException {
LOG.info("Job " + jobKey + " -- INTERRUPTING --");
isJobInterrupted = true;
if (thisThread != null) {
// this called cause the ClosedByInterruptException to happen
thisThread.interrupt();
}
}
private void download(String address, String localFileName) throws ClosedByInterruptException, IOException {
URL url = new URL(address);
ReadableByteChannel src = Channels.newChannel(url.openStream());
WritableByteChannel dest = new FileOutputStream(new File(localFileName)).getChannel();
try {
System.out.println("Downloading " + address + " to " + new File(localFileName).getCanonicalPath());
int size = fastChannelCopy(src, dest);
System.out.println("Download completed! " + (size / 1024 / 1024) + " MB");
} finally {
src.close();
dest.close();
}
}
// Code copied from http://thomaswabner.wordpress.com/2007/10/09/fast-stream-copy-using-javanio-channels/
private static int fastChannelCopy(final ReadableByteChannel src, final WritableByteChannel dest) throws IOException {
final ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
int count = 0;
int total = 0;
while ((count = src.read(buffer)) != -1) {
total += count;
// prepare the buffer to be drained
buffer.flip();
// write to the channel, may block
dest.write(buffer);
// If partial transfer, shift remainder down
// If buffer is empty, same as doing clear()
buffer.compact();
}
// EOF will leave buffer in fill state
buffer.flip();
// make sure the buffer is fully drained.
while (buffer.hasRemaining()) {
dest.write(buffer);
}
return total;
}
}
|
这是我的主类,创建Quartz Scheduler和模拟预期的中断。下载将需要大约40秒完成(59MB文件)。为了看到我们的作业确实是在下载过程中中断,我们启动调度然后休息5秒。注:如果您想看到的作业完成,休息了约40秒。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
package demo;
import static org.quartz.DateBuilder.nextGivenSecondDate;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
// other imports excluded for brevity
public class InterruptExample {
public void run() throws Exception {
final Logger log = LoggerFactory.getLogger(InterruptExample.class);
log.info("------- Initializing ----------------------");
// First we must get a reference to a scheduler
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();
log.info("------- Initialization Complete -----------");
log.info("------- Scheduling Jobs -------------------");
// get a "nice round" time a few seconds in the future...
Date startTime = nextGivenSecondDate(null, 1);
JobDetail job = newJob(MyJob.class).withIdentity("myJob", "group1").build();
SimpleTrigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(startTime)
.withSchedule(simpleSchedule()).build();
sched.scheduleJob(job, trigger);
// start up the scheduler (jobs do not start to fire until
// the scheduler has been started)
sched.start();
log.info("Scheduler thread's name: " + Thread.currentThread().getName());
log.info("------- Started Scheduler -----------------");
try {
// if you want to see the job to finish successfully, sleep for about 40 seconds
Thread.sleep(5 * 1000L);
// tell the scheduler to interrupt our job
sched.interrupt(job.getKey());
Thread.sleep(3 * 1000L);
} catch (Exception e) {
e.printStackTrace();
}
log.info("------- Shutting Down ---------------------");
sched.shutdown(true);
log.info("------- Shutdown Complete -----------------");
}
public static void main(String[] args) throws Exception {
InterruptExample example = new InterruptExample();
example.run();
}
}
|
这是日志,说明我们的作业被intterupted提早退出
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
INFO [main] ------- Initializing ----------------------
INFO [main] Using default implementation for ThreadExecutor
INFO [main] Job execution threads will use class loader of thread: main
INFO [main] Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
INFO [main] Quartz Scheduler v.2.1.3 created.
INFO [main] RAMJobStore initialized.
INFO [main] Scheduler meta-data: Quartz Scheduler (v2.1.3) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
INFO [main] Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
INFO [main] Quartz scheduler version: 2.1.3
INFO [main] ------- Initialization Complete -----------
INFO [main] ------- Scheduling Jobs -------------------
INFO [main] Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
INFO [main] Scheduler thread's name: main
INFO [main] ------- Started Scheduler -----------------
INFO [DefaultQuartzScheduler_Worker-1] Thread name of the current job: DefaultQuartzScheduler_Worker-1
NFO [DefaultQuartzScheduler_Worker-1] Job group1.myJob executing at Mon Apr 16 16:24:40 PDT 2012
Downloading http://d2zwv9pap9ylyd.cloudfront.net/terracotta-3.6.1.tar.gz to S:\quartz-interrupt-demo\terracotta-3.6.1.tar.gz
INFO [main] Job group1.myJob -- INTERRUPTING --
INFO [DefaultQuartzScheduler_Worker-1] Caught ClosedByInterruptException... exiting job.
INFO [DefaultQuartzScheduler_Worker-1] Job group1.myJob did not complete
ERROR [DefaultQuartzScheduler_Worker-1] Worker thread was interrupt()'ed.
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:552)
INFO [main] ------- Shutting Down ---------------------
INFO [main] Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutting down.
INFO [main] Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED paused.
INFO [main] Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutdown complete.
INFO [main] ------- Shutdown Complete -----------------
|
整个例子可以在这里下载
开发杀手移动用户界面的7个步骤
有些微妙,你有一个智能手机,而没有意识到你个人和它已经发展出一种特殊的关系。我们最近了解到,史蒂夫·乔布斯掌管的他自己组建的团队,创建了第一代iPhone——“人们会爱上的电话。”无论我们承认与否,我们与我们的iPhone,黑莓,Android或Windows Phone有着特殊的联系。
但爱是善变的。最近的哈里斯互动研究表明,坏的移动应用程序,可以极大地损害品牌的声誉。几乎有三分之一的受访者表示,当有一个坏的移动应用程序经验时他们告诉过别人。不过,超过半数表示,他们推荐过有良好体验的移动应用程序。
为移动应用提供一个良好的用户体验是至关重要的。这里有七个步骤,让你以正确的方式交付伟大的移动应用程序。
1。定义你的目标
你想完成的移动应用程序是什么?最重要的是,你的用户拿它想要干什么?你必须确定你的应用程序将达成的目标。对于这一点,你需要了解用户用这个程序每天的活动,围绕它的目标和动机。
一个好的方法是创建人物 - 代表用户的虚构人物 - 编写敏捷用户故事规格。这种方法使你以正确的心态来为您的应用程序陈述需求。类似这样的东西:“作为保安人员,约翰尼·布拉沃必须用他的手机,找出下一个他应该到检查点,签到,这样他就可以完成随机巡逻。”这种方法可以让你从用户的角度来看应用。
当然,要考虑移动和桌面应用程序的用户故事之间的差异,尤其是当考虑到移动环境。例如,移动用户可以步行或用一只手。
经过收集几个这些故事后,你必须根据他们的频率区分优先级。我会解释为什么这非常重要。
2。分析现有的应用程序
如果它是第一次你要创建的移动应用程序,仔细看看现有应用的界面。有两个突出的明显的事情:屏幕尺寸很小,所以许多移动设备上使用触摸屏;屏幕上的元素一定要大,所以他们很容易触摸。
较小的屏幕和更大的元素意味着你在屏幕上的显示的项目数量有限。
选择在手机屏幕上怎么样做是一个挑战;一些可用性专家甚至主张,在桌面版本之前创建一个移动网站版本。
区分你的用户故事优先次序对此很有帮助。你要认真研究最重要用户使用的功能。分析出在80%的时间使用的20%的功能,是每一个可用性专家的目标。如果你在移动上做得正确,桌面也将有优先权力。
3。本机应用或移动Web应用?
你需要决定是否使用HTML5或本地API。这一决定对用户界面技术实现有很大的影响。本机应用程序通常运行速度更快,是游戏,离线,硬件密集的应用程序的最佳选择。移动Web应用程序可更快地实施,易于维护,并常常更好地适合企业应用程序。混合的方法也是有道理的,如果你想要移动Web应用程序的维护方便,但需要特定的硬件功能,如手机的摄像头或GPS。
可以利用现有的框架和平台解决方案,但无论您选择什么,确保你可以快速地创建和修改用户界面,以便你可以经常迭代。
4。快速制作原型
项目启动时测试移动界面的一种快速方法是使用低保真原型。Palm Pilot推出之前,它说,发明家把小木块大小的设备放进口袋来感觉如何。后来,他在这上面勾勒了几个粗糙版本的用户界面。
早期的Palm Pilot原型慢慢的接近真实的东西。
移动应用程序原型制作很简单:铅笔和纸都是你所需要的,因为屏幕小,你不会绘制太多。原型测试通过请用户尝试完成他们的任务。看看他们做什么,问他们,他们正在想什么。其结果对帮助您改进设计是惊人的。一个伟大的设计的口头禅是:“不要让我思考!”如果你的移动设计测试失败,那么你的应用程序也将失败。
5。避免破坏 UX 用户体验的错误
当人们开始创建移动应用程序,他们通常会犯一些已知的错误。首先,如果他们创造一个现有网站的移动版本,开发人员常常试图复制桌面上每一个功能,没有考虑到移动用户的意图。
另一个常见的错误是有太多的导航结构。深层结构不能很好地适用于移动,简单的模型才能更好地工作。希望用户能够轻松地输入文字是另一个错误:移动设备上文本输入很难,所以你要尽量减少输入。
在手机上运行优秀的应用是让阅读更多内容而不是书写内容。想一想:您最喜爱的移动应用程序需要输入大量的文字?
6。添加令人愉快的细节
移动电话永远伴随着你,知道你在哪里,这就是为什么mapping地图应用程序是如此的成功。电话可以听到你所听到和看到你所看到的,这也解释了Shazam和Instagram的普及应用。使用位置,摄像头,麦克风,都是获得用户所处的环境聪明的方法,你可以以独特的方式利用它。
即使你不使用移动设备的硬件,也有几个技巧你可以用它来取悦用户。动画可以增加你的应用程序的兴奋。如果您正在创建移动Web应用程序,缓存静态内容,并考虑使用 CDN 内容交付网络,使页面载入更快。
受欢迎触摸移动Web应用程序的另一个是可让用户将它们添加到自己的主屏幕,使他们看起来像本地应用程序的感觉。如果你做到这一点,请记住,在你的用户界面上包含后退按钮,使用户可访问退出选项。
记住用户在不同会话的选择(例如,最近的项目列表),是另一种智能触摸,这将使应用程序具有上下文感知和解放用户不必重复导航步骤。这些可用性能快速完成操作,使用户的生活更轻松。
7。失败宁早,恢复快
即使有设计师和开发人员良好的团队合作,你第一次尝试创建一个移动应用程序可能也会失败。 (最近的一项调查表明,38%的人不满意他们用到品牌应用。)最好的策略是失败宁早和快速迭代,学习人们是如何使用您的应用程序并不断改善。敏捷方法是你交付用户需要的移动用户界面一个伟大的方式。
杀手移动用户界面是很难的,但在这篇文章中概述的战略将有帮助。认真的考虑它们,以你自己的方式来写一些伟大的移动应用程序吧!
在HTML5里用Ajax上传文件到Java Servlet
Ajax file upload to a Java Servlet in HTML5
HTML5带来一个很棒的功能,就是能够使用XMLHttpRequest版本2上传文件。
现代Gecko和WebKit浏览器,包括一个完美的对象参数formdata,允许结合既简单又复杂的表单数据(文本和文件)包含在Ajax请求的对象中。
让我们告诉你如何做到这个。
在这个例子中,我们有两个输入框的表单,一个代表一个简单的文本字段,另一个代表一个文件字段,如下面的代码所示。
<form id="form1">
<label for="sampleText">Please enter a text</label>
<input id="sampleText" name="sampleText" type="text" /> <br/>
<label for="sampleFile">Please select a file
<input id="sampleFile" name="sampleFile" type="file" /> <br/>
<input id="uploadBtn" type="button" value="Ajax Submit" onClick="performAjaxSubmit();"></input>
</form>
<script type="text/javascript">
function performAjaxSubmit() {
var sampleText = document.getElementById("sampleText").value;
var sampleFile = document.getElementById("sampleFile").files[0];
var formdata = new FormData();
formdata.append("sampleText", sampleText);
formdata.append("sampleFile", sampleFile);
var xhr = new XMLHttpRequest();
xhr.open("POST","/fileUploadTester/FileUploader", true);
xhr.send(formdata);
xhr.onload = function(e) {
if (this.status == 200) {
alert(this.responseText);
}
};
}
</script>
正如我们在上面代码中看到,它是一个正常的老XHR的代码,但它有两个差异:
1。在HTML5输入文件的文件属性,这使你能够得到的文件对象。
2。参数formdata对象,其中有一个方法叫做append,允许加入任何形式的数据(文本和文件)的对象。参数formdata对象具有另一大优势,这是Ajax请求“multipart/ form-data”没有任何特殊代码。
现在,让我们继续看Servlet代码(这里用的是Apache Commons File Upload处理multipart表单请求解析)。
public class FileUploader extends HttpServlet {
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String ajaxUpdateResult = "";
try {
List items = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(request);
for (FileItem item : items) {
if (item.isFormField()) {
ajaxUpdateResult += "Field " + item.getFieldName() +
" with value: " + item.getString() + " is successfully read\n\r";
} else {
String fileName = item.getName();
InputStream content = item.getInputStream();
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
// Do whatever with the content InputStream.
System.out.println(Streams.asString(content));
ajaxUpdateResult += "File " + fileName + " is successfully uploaded\n\r";
}
}
} catch (FileUploadException e) {
throw new ServletException("Parsing file upload failed.", e);
}
response.getWriter().print(ajaxUpdateResult);
}
}
Servlet从请求中简单解析multipart表单,然后构造一个结果消息。
请记住,此代码能与Chrome5和Safari5,Firefox 4的工作,但不幸的是将不能在IE9下工作,因为悲惨的IE9没有参数formdata对象,我将告诉你如何可以在IE9实现相同的行为,“我相信你会不喜欢它,因为它是有点难“。
从这里下载完整的代码。
构建移动Web应用程序的技术堆栈
编写web应用程序时,有很多的技术决策。笔者最近回来编写现代Web应用程序,并希望总结一些曾经在开发周期过程中做了记录零散的想法。这篇文章是关于一套对 笔者 最近开发的项目有帮助的框架。 笔者重温了一些最重要的框架类型,其中每一个可以展开来写一篇文章。这并不是一个广泛的现有产品相比,只是一个 笔者 最近使用的部分技术。
虽然笔者的重点是移动优先, 笔者认为,这套技术可以应用在一般的web应用程序。 笔者的决定和数据支持考虑了几个要求:
- 基于JavaScript(CoffeeScript,Dart,绝对值得认真看看,但我想避免引起激进选择)
- 必须在现代浏览器工作良好(IOS 5,Android 4)
挑选一个MVC框架
在本地UI的应用程序开发中模型视图控制器模式已经使用了几十年。其基本思路是分开表示层(用户界面,动画,输入)和数据层(存储,通讯,数据)。有其他类似的模式,如MVVM的(模型视图的ViewModel),但主要的想法是在展现和数据层之间有定义良好的分离,为了更干净的代码和长期的维护:
有许多JavaScript模型视图控制器框架的产品。有一些如Backbone.js和Spine.js是用纯代码编写的,而其他像Knockout.js和Angular依靠DOM数据属性绑定。那些依赖HTML5数据DOM属性的分离视图和数据的MVC系统被认为是不对的。这不包括Knockout.js和Angular框架。 spine.js比 CoffeeScript更容易,根据我最初的要求排除了CoffeeScript。
backbone.js比大多数框架更受欢迎(也许除JavaScriptMVC外,似乎像一个死的项目),还设有一个成长的开源社区。对于笔者的应用程序栈,笔者选择了Backbone.js。欲了解更多有关挑选一个MVC的信息,检出TodoMVC,它使用不同的MVC框架实现相同的Todo应用程序。还可以看到这个MVC框架的比较,它强烈赞成Ember.js,一个出现相对较晚的框架。笔者尚未有机会使用它,但它在我的清单上。
选择一个模板引擎
要在网络上建立一个严谨的应用程序,你不可避免地要建立大型的DOM树。如果使用JavaScript API来操作DOM,不如使用基于字符串的模板编写html来得更简单高效。JS模板已经逐步形成一个奇怪的约定,嵌入模板的内容到脚本标记内:<script id="my-template" type="text/my-template-language">... </script>。使用所有的模板引擎的基本做法是作为一个字符串来加载模板,构建模板参数,然后通过模板引擎模板和参数运行。
backbone.js依赖于Underscore.js,它有一个有些局限的有详细语法的模板引擎。有其他可供选择,包括jQuery模板,Handlebars.js,Mustache.js和许多其他的。 jQuery模板已经被jQuery团队准备废弃了,所以我没有考虑这个选项。Mustache是一个跨语言的模板系统,具有简单和成熟的决定,以支持尽可能少的逻辑。事实上,在Mustache最复杂的构造是遍历一个对象数组的方式。 handlebars.js建于Mustache之上,加入一些不错的功能,如预编译模板和模板表达式。对于笔者而言,并不需要这些额外的功能,然后选择了笔者的模板平台Mustache.js。
在一般情况下,笔者的印象是,现有的模板框架可比较的功能是很少的,因此决定在很大程度上是个人喜好的问题。
选择一个CSS框架
CSS框架是必不可少的工具,用来扩展CSS如变量等方便的功能集,创建分层的CSS选择器的方式,以及一些更先进的功能。这实质上是创建了一个新的语言:CSS的增强版本(姑且称之为它的CSS++)。为便于开发,一些框架在浏览器中实现了一个JavaScript的CSS+ +解释器,而一些其他框架让你监控一个CSS+ +文件,并每当有更改就编译它。所有的CSS框架应提供命令行工具来编译CSS++成CSS给开发。
像模板语言一样,也有很多选择。笔者的选择是出于个人的语法偏好,笔者更喜欢SCSS,因为它避免了像@怪异的语法。 SCSS的一个缺点是,它并没有附带一个JavaScript解释器(有一个非官方的,笔者还没有试过),但可用命令行监视器。还有其他类似的CSS框架,包括LESS和Stylus。
如何布局视图Views
HTML5提供了多种方式来布局内容,MVC框架对这些布局技术的使用无要求,留给开发者你一点困难。
一般来说,对documents相对位置是合适的,但对apps除外。应避免绝对定位,像tables。许多Web开发人员已经转向使用float属性对准元素的,但是这只是第二理想的构建应用程序的观点,因为它没有类似应用程序的布局,导致许多奇怪的问题和臭名昭著的clearfix hacks。
经过多年来的布局与各种网络技术的实验,笔者认为一个固定的定位和flex box的模型相结合是移动互联网应用的理想选择。笔者使用的是将屏幕上的界面元素(页眉,侧边栏,页脚等)固定定位。flex box 模型对在页面上布局堆叠视图(Stacked views)是很棒的(水平或垂直的)。只有CSS盒模型明显地对界面设计进行了优化,非常类似Android的LinearLayout 管理器。对于有关flex box模型的更多信息,请阅读保罗的文章,并注意该规范正在由一个新的,非向后兼容的版本取代。
自适应Web应用程序
最后一节,在这个问题上:笔者大力提倡创建设备特定的用户界面。这意味着为不同的形式屏幕重新编写视图代码部分。幸运的是,MVC模式,使得它比较容易为多个视图(如平板电脑和手机)重用业务逻辑model。
iOS Flipboard演示了这个想法很好,它为平板电脑和手机用户提供了为每个设备外形高度定制的体验。
手机用户界面特别为垂直点击进行了优化,允许单手使用。
平板的UI让两手反面持有设备工作良好。
输入的考虑
移动用户与您的应用程序进行交互的主要方式是通过用手指触摸屏幕。这与基于鼠标的互动相当不同,因为有额外9点在跟踪屏幕,这意味着开发人员编写移动应用程序时,需要抛弃移动鼠标事件。此外,在移动鼠标事件有300ms延迟点击的问题(有一个著名的触摸式的解决方法)。在移动浏览器使用这些事件的详细信息,请参阅我的触摸事件的文章。
只有S /mousedown/ touchstart/所有的事件处理程序是不够的。有 一套 全新的用户期待的触摸设备手势,如点击、通过浏览图像列表导航。虽然苹果公司有一个鲜为人知的手势API,但没有在网页上做手势检测的开放规范。我们真的需要一个JavaScript手势检测库,去处理一些较常见的手势。
如何使其离线工作
对于一个应用程序脱机工作,你需要确保两件事情真实:
- Assets资产可用(通过AppCache,文件系统API等)
- 数据是可用的(通过LocalStorage,WebSQL,IndexedDB等)
实践中,在网络上建立离线应用是一个棘手的问题。一般来说脱机功能应从一开始就加入你的应用程序。让现有Web应用程序没有显着的重写代码运行在离线状态下是特别困难的。此外,脱机技术还有各种未知的存储限制,而且未知超出限制时会发生什么不确定的行为。最后,在离线的技术堆栈还有一些技术问题,最显着的是AppCache,正如我在以前的文章提到。
写真正的离线功能的应用程序是一个非常有趣的方法是“离线优先”。换句话说,如果没有互联网连接全部写入本地,当存在互联网连接,实现同步数据同步层。在Backbone.js MVC模型,这可以很好地适应自定义Backbone.sync适配器。
单元测试
单元测试您的UI是有困难的。然而,因为你使用MVC的模型,它是完全隔离的UI和数据结果,因此,可方便测试。 QUnit是一个相当不错的选择,特别是因为它允许使用它的start()和stop()方法单元测试异步代码。
总结
总之,笔者使用Backbone.js 作为 MVC 框架,Mustache.js做为模板,SCSS作为CSS框架,CSS的Flex box展现界面views,自定义触摸事件和QUnit单元测试工具,来写笔者的移动Web应用程序。脱机支持,笔者仍然尝试用各种技术,并希望未来继续写篇文章。虽然笔者强烈相信有必要在这里列出每种工具(如MVC),笔者也相信,笔者在这里描述的许多具体的技术是可以互换的(如Handlebars 和 Mustache)。
还有一件事:2012年1月17日,Thorax宣布发布。这是一个基于Backbone一套开发库,非常类似我在这篇文章里描述的思想。笔者还没有在任何深度研究,但名称是伟大的:)
使用一套类似的框架吗?有你最喜欢的?觉得笔者缺少一个重要的框架吗?让笔者知道!
