前后端一体化应用
以全栈开发者视角重新审视前后端应用开发体验,发现传统开发体验的提升空间。
随着 Serverless 技术的出现,高可用、高并发和运维能力下沉到了 Serverless 平台底层,对开发者透明,开发者只需要关心自己的业务代码开发。原本门槛很高的后端业务开发变得触手可及,前端开发者更容易掌握后端业务开发。越来越多的开发者转型成为全栈开发者。当以一个全栈开发者视角重新审视前后端应用开发体验时,会发现传统开发体验有很大的提升空间。
Malagu 框架在设计之初就已经意识到了这一点:
- 采用了对 Serverless、前后端一体化更为友好的语言 Typescript
- 提供了前后端通用的 IoC 容器等基础设施
- 抽象了前后端统一的渐进式解决方案:组件化方案
- 前端像调用本地方法一样调用后端接口的 RPC 通信方式
前后端一体化开发优势
如果您不是全栈开发者,您仍然可以基于 Malagu 框架按照传统的前后端分离开发方式开发,与以前相比没有任何区别。如果您是一个全栈开发者,前后端一体化开发可能更加适合您。
Malagu 提供统一的开发语言、IoC 容器、工程化规范、命令行工具、渐进式方案,让您心智负担更小、项目更易维护、后端掌握的技能无缝应用到前端上。甚至前后端代码都可以在同一个项目中开发。
以前,我们只能针对单一的前端或者后端抽象通用代码;现在,我们可以基于前后端一起抽象通用代码,抽象的范围更大,进一步提升代码的复用能力。同时,也不存在前后端开发人员联调和沟通成本,而且更容易做全链路优化。
是否适合大型项目
对于小项目,采用前后端一体化开发,效率肯定是更高的,一个项目丢给一为全栈开发者开发,前后端联调、沟通成本都是省了。但是,对于大型项目,不可能让一个全栈开发者来开发,我们需要借助团队的力量。此时,您可能会觉得前后端一体化开发不适合大型项目。
但是,我们可以换一个角度来看,一个大型项目面对的问题往往是复杂的,我们应该从实现层面跳出来,从架构层面去寻找解决方案。我们可以把一个大型项目从业务视角拆解成许多个小项目,这些小项目再交由全栈开发者开发。
把一个复杂的问题分解成一个个简单的小问题,是一个更为可行的方案。让采用风险更低的渐进式地更新迭代应用的方案成为可能。
团队组织方式
在过去,前后端分工,按照技术类型纵向地组织团队;现在,前后端一体化开发,将一个大型应用从业务视角拆解成许多个微应用,按照业务功能横向地组织团队。
前后端一体化开发体验示例
以前端调用后端接口这样一个十分常见的场景为例,使用前后端一体化开发的流程是:
- 定义接口
- 后端实现接口
- 前端调用接口(像调用本地方法一样调用后端接口)
定义接口
export const UserService = Symbol("UserService");
export interface UserService {
list(): Promise<User[]>;
get(id: number): Promise<User | undefined>;
remove(id: number): Promise<void>;
modify(user: User): Promise<void>;
create(user: User): Promise<User>;
}
export interface User {
id: number;
name: string;
age: number;
}
后端实现接口
import { Transactional, OrmContext } from "@malagu/typeorm/lib/node";
import { Rpc } from "@malagu/rpc";
import { User } from "./entity";
import { UserService } from "../common";
@Rpc(UserService) // 相当于告诉框架,可以让前端通过 JSON RPC 的方式调用
export class UserServiceImpl implements UserService {
@Transactional({ readOnly: true }) // 开启只读事务
list(): Promise<User[]> {
const repo = OrmContext.getRepository(User);
return repo.find();
}
@Transactional({ readOnly: true })
get(id: number): Promise<User | undefined> {
const repo = OrmContext.getRepository(User);
return repo.findOne(id);
}
@Transactional() // 开启读写事务
async remove(id: number): Promise<void> {
const repo = OrmContext.getRepository(User);
await repo.delete(id);
}
@Transactional()
async modify(user: User): Promise<void> {
const repo = OrmContext.getRepository(User);
await repo.update(user.id, user);
}
@Transactional()
create(user: User): Promise<User> {
const repo = OrmContext.getRepository(User);
return repo.save(user);
}
}
前端调用接口
import * as React from "react";
import { View } from "@malagu/react";
import { RpcUtil } from "@malagu/rpc";
import { DataTable } from "grommet";
import { UserService, User } from "../common";
function Users() {
const [data, setData] = React.useState<User[]>([]);
React.useEffect(() => {
// 通过 RpcUtil 获取接口 UserService 的远程调用代理对象
const userService = RpcUtil.get<UserService>(UserService);
// 像调用本地方法一样调用后端接口
userService.list().then((users) => setData(users));
}, []);
return (
<DataTable
margin={{ vertical: "medium" }}
columns={[
{
property: "name",
header: "Name",
primary: true,
},
{
property: "age",
header: "Age",
},
]}
data={data}
></DataTable>
);
}
@View({ component: Users })
export default class {}