【发布时间】:2019-11-15 14:30:14
【问题描述】:
我正在使用 Typescript 将 REST API 端点编写为 Firebase 上的函数,并且这些方法都遵循类似的模式:检查 request.body,从该主体数据中提取适当的数据,将其放入一个强-类型化对象,使用该对象通过一些数据访问代码将数据推送到数据库。在多次编写相同的基本数据提取逻辑来处理 request.body 之后,我认为必须有一种方法来抽象这项工作。我对此有三个要求:(1)该方法应该可以从我的任何数据模型的 request.body 中提取数据。 (2) 数据模型应该是完全自描述的,这样它们不仅描述了数据应该具有的属性,而且当需要一组特定的属性时它们可以关联起来。 (3) 该方法应该能够从数据模型中分辨出需要哪些属性,并对通过 request.body 传递的数据进行一些验证。
作为#2 的示例,模型是自描述的:例如,当我创建一个新的数据记录时,我不需要 ID,因为如果它不存在,我可以创建它在函数中并将其传回。另一方面,在这种情况下需要 的“名称”属性。相比之下,update 方法需要记录 ID(因此它知道要更新哪条记录),但 不需要 需要“名称”,除非那是实际存在的修改的。
我的方法是(1)在一个单独的类上使用静态工厂方法,该方法采用需要创建的数据模型的类类型;预期的操作(即 create、read、update 或 delete);和请求正文。 (2) 一组数据模型类,基本上只描述数据并在需要的地方包含一点验证逻辑,还包括字段名称和相关要求值的(静态)列表(存储为四位,其中每个位置代表一个四个 CRUD 操作中的一个。) (3) 一个通用接口,以便静态工厂方法知道如何处理不同的数据对象以获取那些字段名称和使用标志。
这是我的静态工厂方法:
static create<T extends typeof DataObjectBase>(cls: { new(...args: any[]): T; }, intendedOperation: number, requestBody: any) : T {
let dataObject : T = null;
const sourceData = {};
const objFields = cls.fieldNames;
const flagCollection = cls.requiredUseFlags();
const requiredFields = flagCollection.getFieldsForOperation(intendedOperation);
if (requestBody) {
// parse the request body
// first get all values that are available and match object field names
const allFields = Object.values(objFields); // gets all properties as key/value pairs for easier iteration
// iterate through the allFields array
for (const f in allFields) {
if (requestBody.hasOwnProperty(f)) {
// prop found; add the field to 'sourceData' and copy the value from requestBody
sourceData[f] = requestBody[f];
} else if (requiredFields.indexOf(f)>-1) {
// field is required but not available; throw error
throw new InvalidArgumentError(`${cls}.${f} is a required field, but no value found for it in request.body.`, requestBody);
}
}
dataObject = (<any>Object).assign(dataObject, sourceData);
} else {
throw new ArgumentNullError('"requestBody" argument cannot be null.', requestBody);
}
return new cls();
}
这是一个数据模型类的示例:
export class Address extends DataObjectBase {
constructor(
public id : string,
public street1 : string,
public street2 : string = "",
public city : string,
public state : string,
public zip : string) {
// call base constructor
super();
}
static fieldNames = {
ID = "id",
STREET1 = "street1",
STREET2 = "street2",
// you get the idea...
}
static requiredUseFlags() {
ID = READ | UPDATE | DELETE,
STREET1 = 0,
// again, you get the idea...
// CREATE, READ, UPDATE, DELETE are all bit-flags set elsewhere
}
}
我希望能够像这样调用上面的create 方法:
const address = create<Address>(Address, CREATE, request.body);
最初,我尝试了这样的签名:
static create<T extends typeof DataObjectBase>(cls: T, intendedOperation: number, requestBody: any) : T
但是,当我这样做时,我收到一个错误,“地址是一种类型,但被用作一个值。”一旦我将其更改为上面的内容,我就不再收到该错误并开始收到
Property 'fieldNames' does not exist on type 'new (...args: any[]) => T'
注意:我也尝试过使用两个接口来描述(第一个)实例方法和(第二个)静态方法的技巧,然后让静态接口扩展实例接口,然后基类实现静态接口等,如here、here、here等。这也没有让我到达那里。
我当然愿意承认我很可能过度设计了这一切,我很乐意接受其他更简单的建议。但我认为必须有一种方法来完成我想要的,并且避免一遍又一遍地编写相同的基本请求体解析代码。
【问题讨论】:
-
DataObjectBase是抽象的吗? -
这不是抽象的。我在它的所有子类中都使用了许多与数据相关的通用方法。
-
无论哪种方式,下面的解决方案都应该有效
标签: typescript typescript-generics