近期开始研究Facebook f8app项目,目标是理解Facebook官方React Native f8app的整体技术架构,给公司目前几个的React Native项目开发提供官方经验借鉴,并对原生开发和React Native开发进行框架层面的融合。
研究了f8app的项目结构后,发现f8app服务器端的代码比较少,并且弄清楚数据模型和接口协议,对于后面理解客户端的实现有帮助,所以先分析服务器端。第二篇文章会详细分析f8app服务端的技术实现,并进行一些修改实验。f8app服务器端是基于Node.js,Express,Parse和GraphQL实现的,数据存储是MongoDB NoSql数据库。
安装推荐开发工具nuclide
我们一般使用Atom进行js开发,首先安装facebook推荐的开发工具nuclide。Mac和Linux下的安装命令如下,暂不支持windows。
apm install nuclide
F8app工程结构
下面先用tree -L 2
命令看下目录结构,只显示二级目录
├── LICENSE
├── README.md
├── android 安卓项目目录
│ ├── app
│ ├── build.gradle
│ ├── gradle
│ ├── gradle.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── settings.gradle
├── index.android.js
├── index.ios.js
├── ios iOS项目目录
│ ├── Default-568h@2x.png
│ ├── F8Scrolling.h
│ ├── F8Scrolling.m
│ ├── F8v2
│ ├── F8v2.xcodeproj
│ ├── F8v2.xcworkspace
│ ├── F8v2Tests
│ ├── PodFile
│ ├── Podfile.lock
│ ├── Pods
│ ├── Settings.bundle
│ ├── Splash@2x.png
│ └── build
├── js React native js源代码目录
│ ├── F8App.js
│ ├── F8Navigator.js
│ ├── FacebookSDK.js
│ ├── Playground.js
│ ├── PushNotificationsController.js
│ ├── actions
│ ├── common
│ ├── env.js
│ ├── filter
│ ├── flow-lib.js
│ ├── login
│ ├── rating
│ ├── reducers
│ ├── setup.js
│ ├── store
│ └── tabs
├── logs
├── node_modules
│ 下面有900多个js模块
├── npm-shrinkwrap.json
├── package.json
├── scripts 一些脚本
│ ├── import-data-from-parse.js
│ ├── optimize-images.sh
│ └── store-ip.sh
└── server 服务器端目录,基于Parse和GraphiQL
├── cloud
├── cloud.js
├── schema
└── server.js
服务器端技术介绍
f8app的服务器代码都在server目录下,由于使用了Node.js,Express,Parse,GraphQL等先进框架,代码量比较小,一共只有10个js文件,996行js代码。下面先介绍下f8app服务器端使用的技术,Node.js和Express这类常见技术就不介绍了。
服务器端是基于Facebook的Parse云平台,Parse服务将于January 28, 2017停止服务,现在官方提供了迁移指南。
现在Parse Server已经开源了,git地址是parse-server,开源Parse Server是基于node.js和Express框架的,支持的数据库是MongoDB和MongoRocks。
script/import-data-from-parse.js
实现了从https://api.parse.com/1/classes/ 导入数据到本地MongoDB数据库的功能,通过npm run import-data
可以导入数据。
MongoRocks: What and Why?
MongoRocks是Facebook在2015年开发的基于RocksDB的MongoDB存储引擎实现,一般情况下Parse应用都应该用MongoRocks。
MongoDB3.0允许用户替换默认的default memory mapped (MMAP) 存储引擎。MongoRocks比MMAP存储引擎在性能方面有很多好处,
MongoRocks更多介绍可以参考MongoRocks链接
Parse Server简单介绍
Parse 的愿景是让开发者摆脱服务器,随心所欲地开发各种移动应用程序。对于比较复杂的应用程序,开发者有时需要不在移动设备上运行的逻辑。Cloud Code 正好可以实现这一目标。
由于f8app已经带了Parse Server,所以我就不需要用npm安装了,直接在f8app目录下运行npm start,测试下添加数据
curl -X POST \
-H "X-Parse-Application-Id: oss-f8-app-2016" \
-H "Content-Type: application/json" \
-d '{"score":1337,"playerName":"Sean Plott","cheatMode":false}' \
http://localhost:8080/parse/classes/GameScore
在终端运行上面的命令后,服务端返回 {"objectId":"S8mgRPVgwU","createdAt":"2016-05-17T08:31:10.802Z"}
表示添加成功
在浏览器,可以看到刚才添加了一条Game score数据。
在终端运行下面命令可以查询新添加的那条数据
curl -X GET \
-H "X-Parse-Application-Id: oss-f8-app-2016" \
http://localhost:8080/parse/classes/GameScore/S8mgRPVgwU
返回结果 {"objectId":"S8mgRPVgwU","score":1337,"playerName":"Sean Plott","cheatMode":false,"updatedAt":"2016-05-17T08:31:10.802Z"
Parse提供各种语言的SDK,包括Android,iOS,js等,可以很方便的在移动端使用。
Relay和GraphQL
Relay 是 app 中的一个数据框架,GraphQL 是 Relay 用来做数据表示的查询语言。GraphQL 同时还有一个 npm 包,可在服务器上运行,提供可以和 Relay 交互的数据源.
Relay 不是从 Flux 架构分化来的,它只和 GraphQL 有关。这也就是说,这和 Redux 的模型有着巨大的区别。
在 Relay + GraphQL 的模型中,每个组件指定自己需要的数据。Relay 调用数据,数据更新时,提供给组件最新的数据,并在客户端做缓存。app 需要更新数据时,在 Action 中创建一个 GraphQL 变更,这和 Redux 类似。
GraphQL是Facebook开发的一个针对描述复杂数据模型需求的一个数据查询语言标准,目前是工作草案。可以使用多种语言实现,目前Facebook提供了node.js版本的参考实现GraphQL.js..
服务器端代码分析
server.js是服务器的入口类,基于Express框架默认在8080端口提供了/parse,/dashboard,/graphql三个url入口,根目录默认跳转到/graphql。
下面是/parse url入口的代码,很简单,直接new ParseServer,把相关的参数传进去,mongodb数据库地址DATABASE_URI可以取环境变量的值const DATABASE_URI = process.env.DATABASE_URI || 'mongodb://localhost:27017/dev';
,其它的参数也都可以直接在环境变量里面设置,通过export XXX="xxx"即可修改。
server.use(
'/parse',
new ParseServer({
databaseURI: DATABASE_URI,
cloud: path.resolve(__dirname, 'cloud.js'),
appId: APP_ID,
masterKey: MASTER_KEY,
fileKey: 'f33fc1a9-9ba9-4589-95ca-9976c0d52cd5',
serverURL: `http://${SERVER_HOST}:${SERVER_PORT}/parse`,
})
);
cloud.js很简单,只是引用了cloud目录下文件,代码如下:
require('./cloud/friends');
require('./cloud/surveys');
require('./cloud/tests');
cloud/friends.js 提供通过OAuth在Facebook登录后,获取好友和日程的功能。
/dashboard是ParseDashboard的入口,很简单不说了,就是Parse的Web管理控制台,通过http://localhost:8080/dashboard 可以访问,对Mongodb中数据进行管理,操作很方便。
/graphql是通过express-graphql插件实现的一个graphql入口,前端UI是通过graphiql.js插件实现的。graphql这块占代码量比较大,schema目录下的文件都是和graphql相关的。
GraphQL.js. 提供2个主要能力:构建type schema; 根据type schema类型提供查询能力。
schema.js是graphql查询的入口,只支持查询schedule,node(接口类型,只有id),viewer,这点可以从下面的代码看出来,只指定了F8QueryType。我们在浏览器graphql控制台http://localhost:8080/graphql 里面也只能做schedule的查询,否则会提示错误,这个控制台还是很智能的,支持查询代码提示。
export var Schema = new GraphQLSchema({
query: F8QueryType,
mutation: F8MutationType,
});
F8QueryType 定义如下:
var F8QueryType = new GraphQLObjectType({
name: 'Query',
fields: () => ({
node: nodeField,
viewer: {
type: F8UserType,
resolve: (rootValue) => rootValue, // TODO: Authenticate user
},
schedule: {
type: new GraphQLList(F8SessionType),
description: 'F8 agenda',
resolve: (user, args) => new Parse.Query(Session)
.ascending('startTime')
.find(),
},
}),
});
修改下试试,让它能支持查询演讲者,在schedule:{这行上面加入下面代码:
speaker: {
type: new GraphQLList(F8SpeakerType),
resolve: (speaker, args) => new Parse.Query(Speaker)
.find(),
},
在graphql控制台输入查询条件
{
speaker{
id
name
title
}
}
可以查到所有演讲者的列表。
schema.graphql直观的展示了全部的type scheme查询类型,可以看到有interface和type 2种定义方法,interface其实就是类似Java里面的接口,type可以实现interface。Node是interface,包含一个主键id,其它很多类型都实现了Node,例如type Speaker implements Node。通过这个文件我们可以轻松地理解整个服务器的数据结构,还是很清晰的。graphiql.js控制台第一次访问会请求schema.json,用于查询框智能语法提示。
updateSchema.js可以重新生成schema.graphql和schema.json。在f8app目录下运行npm run update-schema
命令可以重新生成这2个文件。
按照上面的做法修改文件后运行命令,可以发现 schema.graphql多了一行 speaker: [Speaker]
babelRelayPlugin.js代码很少,是提供scheme数据给Relay的。
具体可以参考babel-relay-plugin
本文介绍了f8app的项目架构,分析了f8app服务器端的技术实现,整个数据流是Parse Cloud 通过脚本导入-> MongoDB -> ParseServer -> GraphQL -> Redux -> ReactNative,用到了Express,Parse Server,Parse dashboard,GraphQL.js,GraphiQL.js, Ralay等js模块,代码量比较小,但技术栈挺复杂的。
下篇文章分析React Native Android端的技术实现。