开发 todo service
Todo app 功能介绍#
我们即将开发的建议 todo-app ,界面相对简陋,大概如下图所示:
它包含如下功能:
- 输入新的 todo content,并按回车添加
- 对于每个 todo 项
- 可以切换
completed/active状态 - 可以删除
- 可以双击启动编辑
- 可以切换
- 可以清楚所有
completed状态的 todo 项。
我们需要实现一组服务端 api,支持:
getTodos获取当前的todosaddTodo添加新的todoremoveTodo删除todoupdateTodo更新todoclearCompleted清楚所有已完成的todo
然后在前端通过 farrow-api 复用服务端的类型和接口调用代码,基于 react 开发界面。
Todo Service#
新建 server/api/todo.ts 文件,引入相关模块。
然后定义 Todo Schema,并通过 TypeOf 提取 Todo Schema 包含的类型。然后,我们用 farrow-schema 的 List 函数,构造 Todos Schema。
请注意,上面看起来虽然有点冗余,但每一部分都有它的作用,比如 description 在 codegen 到前端之后,变成了注释,是 api 文档化的一个途径。
前端获取到的类型如下所示:
getTodos#
接下来,我们可以定义第一个接口 getTodos。
getTodos 虽然不需要参数,但 input schema 还是需要设置 {},因为目前 farrow-api 还不支持设置不带参数的接口契约。
getTodos 的 output schema 为 { todos },然后我们用内存里 todos 数组模拟数据库,在 getTodos 接口的函数体内直接返回 { todos }。
一个简单的接口就实现了。
addTodo#
接下来,我们来定义 addTodo 接口。
addTodo 接口,包含非空的 input schema 和非空的 output schema,比 getTodos 接口的实现更复杂一些。
前面通过 class Todo extends ObjectType 声明 Todo Schema 时,我们利用了 class 自带的 Todo.name 可以访问到 class name。
然而,const AddTodoOutput = Union(InvalidAddTodoInput, AddTodoSuccess); 的 AddTodoOutput 是普通变量名,需要配置 displayName 属性。
这个属性不是必要的,不配置它也能工作。但 AddTodoOutput 在 codegen 时会被内联到每一处使用它的地方,前端也难以直接通过类型名称索引到它。
配置 displayName 之后,代码生成结果如下:
另一个值得强调的是,我们采用了 Discriminated Unions 或者说 Tagged Unions 去表达 Success/Failure 的互斥关系,而非使用 Http status code。
我们可以像下面那样消费数据。
通过 Tagged Unions,后端接口可以强制让前端处理不同的 case, TypeScript 会通过类型检查,约束前端对数据的消费方式。
removeTodo#
接下来,我们添加 removeTodo 接口,跟 addTodo 接口差不多。
- 定义
input schema,此处是RemoveTodoInput - 定义
output schemas,通过Tagged Unions合并成output schema - 用
input schema+output schema去定义api schema - 实现这个接口
值得一提的是,Schema.create(value) 方法,如 RemoveTodoSuccess.create,其实内部并没有特殊处理,它仅仅返回 value。
create 方法的用途是为了让类型推导更准确,当我们采用 Tagged Unions 模式时,常常用 Literal String Type 作为 Tag 字段的类型。直接使用字面量,如'RemoveTodoSuccess' ,常常会被推导为 string 类型。通过辅助的 create 方法,我们可以更准确地推导出 Literal type。
updateTodo#
接下来,我们实现 updateTodo 接口。
这里有意思的地方是,如何定义一个可选的字段?
通过 ?= 配合 Nullable(X) 我们可以定义一个可选字段。
clearCompleted#
实现 clearCompleted 接口
跟 getTodos 一样简单。
ApiService#
其实,上述实现的 getTodos, addTodo, removeTodo 等接口,本质上都是函数,只是通过 Schema 定义,我们可以推导出 TypeScript 类型,以及在 runtime 也能拿到一些 metadata。
它们只是函数,我们需要将它绑定到一个 http server 上,通过下面的代码来完成。
ApiService 可以将一组 entries 函数,转化成 farrow-http 的 router 对象,可以挂在到 HttpPipeline 上。
至此,我们完成了 TodoService 的服务端接口部分,接下来,让我们来看看前端消费接口数据的部分。