mittwald API模型
这个包包含了一组用于连贯交互的领域模型 使用mittwald API。
许可证
版权所有:Mittwald CM Service GmbH & Co. KG
本项目(以及其中的所有NPM软件包)在MIT许可下获得许可; 看到 许可证 文件查看详细信息。
安装
你可以从普通的NPM注册表中安装这个包:
yarn add @mittwald/api-models
设置
您将需要初始化API客户端,以便对模型进行操作
由这个包提供。使用 api 初始化的全局实例
一些方法。
import { api } from "@mittwald/api-models";
api.setupWithApiToken(process.env.MW_API_TOKEN);
示例
- 一、
Reference或者ReferenceModel代表一个特定的模型 ID。 - 一、
DetailedModel包含资源的所有数据。
有关更详细的描述,请参阅本节 模型类型
// Get a detailed project
const detailedProject = await Project.get("p-vka9t3");
// Create a project reference
const projectRef = Project.ofId("p-vka9t3");
// Get the detailed project from the reference
const anotherDetailedProject = await projectRef.getDetailed();
// Update project description
await detailedProject.updateDescription("My new description!");
// This method just needs the ID and a description and
// thus is also available on the reference
await projectRef.updateDescription("My new description!");
// Accessing the projects server reference
const server = project.server;
// List all projects of this server
const serversProjects = await server.projects.execute();
// List all projects
const allProjects = await Project.query().execute();
// Iterate over project List Models
for (const project of serversProjects) {
await project.leave();
}
React中的用法
这个包还提供了在React组件中使用的方法。它 使用 @mittwald / react-use-promise 将所有异步函数封装到AsyncResources中。更多的细节 关于如何使用AsyncResources,请参阅包文档。
安装
要使用React客户端,您必须安装附加的
@mittwald/react-use-promise 包:
yarn add @mittwald/react-use-promise
所有异步方法都提供 use方法属性。这种方法使用
@mittwald / react-use-promise
在底层以“React的方式”“解析”承诺。
const detailedProject = Project.get.use("p-vka9t3");
// Create a project reference
const projectRef = Project.ofId("p-vka9t3");
// Get the detailed project from the reference
const anotherDetailedProject = projectRef.getDetailed.use();
// Accessing the projects server reference
const server = project.server;
// List all projects of this server
const serversProjects = server.projects.execute.use();
// List all projects
const allProjects = Project.query().execute.use();
不变性和状态更新
这个包提供的大多数模型都代表了一个相关的对应部分 在后端。当从后端加载模型时,当前状态为 合并到模型实例中。为了保持简单和可预测 状态是不可变的,在任何情况下都不会改变. 结果是 您必须创建一个新实例来获取更新的模型并传播它 整个运行时代码。
这也适用于在客户端发起的操作。例如,当
updateDescription 方法在项目上调用时,项目实例将调用
仍然有旧的描述。
“关注变化”不在本包的范围内,将在 未来版本或其他包™️。
贡献
作为一般的建议,在贡献时,一定要查看现有的源代码 编写代码并将其用作模板!
在添加或修改模型时,请考虑以下约定。
文件结构
在有意义的目录中构建模型。
使用基类
模型应该扩展(或继承)正确的基类。你可以找到底
类 src/base. 以下是可用的类。
DataModel
数据模型是所有模型类的基础,这些模型类包含一组 不可变的通用数据。
ReferenceModel
ReferenceModel仅通过其ID表示某个模型。作为最基本的 模型操作通常只需要ID和一些输入数据(删除、重命名、 …),参考模型可以避免不必要的API往返。
坚持使用通用语言
在添加模型或方法时,请密切关注(可能存在的) 在各自领域中使用的语言。如果是的话,可以和负责的团队谈谈 不确定的。
作为抽象层的模型
模型应涵盖以下几个方面:
- 业务逻辑的一致表示
- 简单加载模型及其链接实体
- 以直观的方式与模型交互的方法
- 对模型原始数据进行预处理,增加DX
- 一致的API
模型类型
详细模型vs.列表模型
加载单个项目或组件时,某些模型的响应类型通常不同
列表。为了减少数据量,列表响应类型通常是一个子集
的综合模型。为详细模型添加单独的类(命名它)
[Model]Detailed)和列表模型(命名它 [Model]ListItem)。
如果两个模型共享一个公共代码库,您应该添加一个公共模型(命名它)
[Model]Common)。
参考模型
参考模型仅通过其ID表示某个模型。作为最基本的 模型操作通常只需要ID和一些输入数据(删除、重命名、 …),参考模型可以避免不必要的API往返。这些类 应该用作新创建模型或链接模型的返回类型。
要得到实际的详细模型,参考模型 必须 有一个
function getDetailed(): Promise<ModelDetailed> 和
function findDetailed(): Promise<ModelDetailed|undefined> 方法。
在实现入口点模型时考虑扩展参考模型。
查询模型
查询模型通常需要一个查询对象——或者短查询。查询
主要包括分页设置,如 limit, skip 或者 page. 它也可能
包括以下过滤器 fromDate 或者 toDate,并过滤到其他模型,如
customerId.
查询模型表示对特定模型的特定查询,并且应该包括 以下方法:
execute():执行查询并返回相应的List Modelrefine(overrideQuery):创建一个新的查询模型,其中包含一个精细化的查询对象getTotalCount():获取查询的总计数(使用)limit: 0)
当一个模型支持查询时,它应该提供一个静态 query() 方法
创建各自的查询模型。
当一个模型被用作查询模型中的查询参数时,该模型应该 在该查询模型的参考模型中有一个属性。请参阅以下内容 例子:
class Server {
public readonly projects: ProjectsListQuery;
public constructor(id: string) {
this.projects = new ProjectListQuery({
server: this,
});
}
}
列表模式
列表模型是查询模型执行的结果。列表模型包括
- 各自列表模型的列表,受分页配置的限制
- 可用的总数(用于实现分页或计数数据)
列表模型应该扩展它们各自的查询模型,因为它可能是
也可以叫 refine() 在已执行的查询上。
实现细节
在实现共享功能时,例如在公共模型中,您可以使用
这个 polytype 图书馆实现
动态多重继承。请务必查看现有的源代码
实现的例子。
入口点模型
提供一个模型(命名它) [Model])作为所有不同的入口点
模型类型(详细的、查询的……)。作为惯例,为
这个模型。
使用正确的动词
find
入口点模型应该有一个静态的 find 方法。find方法返回
详细的模型或可返回 undefined 如果找不到型号。
get
除了 find 方法入口点模型应该有一个静态的 get
方法。get方法应该返回所需的对象或抛出一个
ObjectNotFoundError. 你可以使用 find 方法并断言存在
与 assertObjectFound 函数。
query
当需要查询对象列表时,使用 query 方法。它可能支持
query 参数以按给定条件过滤结果。
create
当应该创建模型时,请使用静态 create 方法。这种方法应该
返回已创建资源的引用。
访问“链接”模型
大多数模型都是更大的模型树的一部分。模型应该提供
属性来获取父模型和子模型,比如 project.server,
server.projects 或者 server.customer.
使用参考模型。在可能的情况下使用入口点模型!
如果链接的模型提供了参考模型或入口点模型,则在 模型构造函数,以避免不必要的API往返。
模型行为的抽象
模型通常由一组行为支持,这些行为定义了基本模型 交互。为了实际“使用”模型,必须将其初始化为 具体的行为实现。这个抽象层移除 从模型中实现特定的代码,并生成行为 可交换而不会对模型本身产生任何影响-例如在单元内部 测试。
考虑使用以下行为:
- API交互
- 存储和加载本地数据
- 复杂的计算或逻辑(可能由外部库支持)
将API交互封装在行为中
将任何API交互封装在模型行为中以防止强 模型和api特定实现的耦合。
不要🥴
class ProjectDetailed {
public static async find(
id: string,
): Promise<ProjProjectDetailed | undefined> {
const response = await client.project.getProject({
id,
});
if (response.status === 200) {
return new Project(response.data.id, response.data);
}
assertStatus(response, 403);
}
}
做😃
class ProjectDetailed {
public static async find(id: string): Promise<ProjectDetailed | undefined> {
const data = await config.project.behaviors.find(id);
if (data !== undefined) {
return new Project(data.id, data);
}
}
}
如何实现行为
放置一个 behaviors 模型内的文件夹,看起来应该是这样的:
Project/
├─ behaviors/
│ ├─ index.ts
│ ├─ types.ts (behavior interface)
│ ├─ api.ts (behavior implementation)
│ ├─ inmem.ts (behavior implementation)
定义 types.ts 第一个
首先实现行为的接口是一个很好的起点。 接口通常只定义行为中使用的方法。就像
export interface ProjectBehaviors {
find: (id: string) => Promise<ProjectData | undefined>;
updateDescription: (projectId: string, description: string) => Promise<void>;
}
然后在全局行为配置中注册该行为
packages/models/src/config/config.ts.
使用模型中的行为
如果定义了行为接口,就可以开始实现模型了。你 还可以先实现具体的API行为,来“证明”该行为是什么 “使用”真正的API。
import { config } from "../../config/config.js";
class ProjectDetailed {
public static async find(id: string): Promise<ProjectDetailed | undefined> {
const data = await config.project.behaviors.find(id);
if (data !== undefined) {
return new Project(data.id, data);
}
}
}
实现API行为
API行为依赖于API客户端。您可以将该行为实现为 对象工厂,或实现接口的简单类。在使用 对象工厂,您不必重新声明方法参数类型。
做特定于实现的事情,从而准备和执行请求, 最后处理响应。
import { ProjectBehaviors } from "./types.js";
import { assertStatus, MittwaldAPIV2Client } from "@mittwald/api-client";
export const apiProjectBehaviors = (
client: MittwaldAPIV2Client,
): ProjectBehaviors => ({
find: async (id) => {
const response = await client.project.getProject({
projectId: id,
});
if (response.status === 200) {
return response.data;
}
assertStatus(response, 403);
},
});
为React做准备
所有异步方法都应该提供 use方法属性。这个方法
使用
@mittwald / react-use-promise
在底层以“React的方式”“解析”承诺。
为您提供此功能 异步 建模方法,包装实际方法
与 provideReact 增强剂。
class ProjectDetailed {
public static find = provideReact(
async (id: string): Promise<ProjectDetailed | undefined> => {
const data = await config.behaviors.project.find(id);
if (data !== undefined) {
return new ProjectDetailed(data);
}
},
);
}