使用Keystone 6为B2B网站设计不同产品的模式



我正在构建一个B2B批发网站,使用Nextjs和Apollo Client作为前端,Keystonejs运行后端。这个问题更多的是针对后端和设置Keystonejs的模式。

这个网站是基于Wes Bos的一个教程,https://advancedreact.com/.我希望能扩展这个想法,到目前为止基本上都是成功的。直到我意识到产品进入并不像他的例子那样容易。

首先,请原谅我对这个项目的后台一无所知,因为我可能使用了错误的术语。我说的是模式,但也许我指的是数据库设计?我知道Keystone也把它们称为列表。所以Product、CartItem、Order都是列表。像这样:

export const Product = list({
fields: {
name: text({ validation: { isRequired: true } }),
slug: text({ isIndexed: 'unique', label: 'Pretty URL)'}),
hotdeal: checkbox({ label: 'Hot Deal?' }),
inventory: decimal(),
price: integer(),
category: relationship({
ref: 'Category.product',
}),
photo: relationship({
ref: 'ProductImage.product',
many: true,
ui: {
displayMode: 'cards',
cardFields: ['image', 'altText'],
inlineCreate: { fields: ['image', 'altText'] },
inlineEdit: { fields: ['image', 'altText'] },
},
}),
}
});

我的产品将分为几个类别。在每个类别中,产品在Keystone中将有不同的字段。例如,我们可能有一个笔记本电脑类别。然后是t恤的另一个类别。笔记本电脑的产品字段可能是:

  • 品牌
  • 型号
  • 名称
  • 价格
  • CPU
  • 内存
  • 屏幕大小

T恤产品领域可能有:

  • 姓名
  • 尺寸
  • 颜色
  • 材料
  • 价格

您可以看到他们共享的唯一常见字段是名称和价格。所以仅仅为产品制定一个模式是行不通的。因为为笔记本电脑填写一张显示尺寸、颜色和材料的表格是没有意义的。在输入新t恤时,看到CPU/内存/屏幕大小字段也没有意义。

我想我可以为每个类别创建单独的模式。因此,每个类别的唯一字段没有显示在其他类别中。然后,我将创建一个与每个类别都有关系的主产品模式文件。但对我来说,这听起来过于复杂,而且不可扩展,因为可能会添加新的类别。

那么,我该如何以一种对独特产品有意义的方式来设置模式,使Keystone中的产品条目不仅仅是一个包含所有可能的产品字段的永无止境的形式呢?但这是一种灵活且可扩展的方法。也许我的工具是有限的,而使用Keystone是不可能的。

注意:这不是一个全面的电子商务网站。不会在网上进行任何交易或销售。它只会处理订单。

这是我的回购链接。https://github.com/brudolph/green-mountain-cannabis/tree/main/backend

更新:所以@Molomby给出的答案让我思考父类Product和子类ShirtProductLaptopProduct之间的关系如何;d工作。

就像你提到的@Molomby一样,我有Product类、ShirtProductLaptopProduct子类。每个子类通过relationship字段引用Product类。但这只是一种片面的关系。为了使其具有两面性,以便Product了解子类,我似乎需要为Product中的每个子类提供一个relationship字段(参见下面的示例)。我的假设正确吗?这听起来不太可扩展。我的客户提到可以添加新的类别,这是可能的,因为我有一个Category列表,但每次都需要创建新的类别类。因此,例如,他添加了";狗粮";,我需要创建DogFoodProduct类。

export const Product = list({
fields: {
name: text({ validation: { isRequired: true } }),
slug: text({ isIndexed: 'unique', label: 'Pretty URL)'}),
hotdeal: checkbox({ label: 'Hot Deal?' }),
inventory: decimal(),
price: integer(),
category: relationship({
ref: 'Category.product',
}),
shirt: relationship({
ref: 'ShirtProduct',
}),
laptop: relationship({
ref: 'LaptopProduct',
}),
dogFood: relationship({
ref: 'DogFoodProduct',
}),
photo: relationship({
ref: 'ProductImage.product',
many: true,
ui: {
displayMode: 'cards',
cardFields: ['image', 'altText'],
inlineCreate: { fields: ['image', 'altText'] },
inlineEdit: { fields: ['image', 'altText'] },
},
}),
}
});

TL;DR:这是数据建模中的一个常见问题。如果您有时间构建一个,Keystone的自定义字段类型是我为这个用例建议的解决方案。快看我的笔记了。


当试图将类层次结构映射到关系数据库中的表(或Keystone模式中的列表,实际上是一样的)时,您遇到的问题是一个公认的问题。事实上,我以前在用户类型的上下文中写过它。

在这种情况下,您有一个Product类,其中包含LaptopProductShirtProduct子类。你是否认为这些是JavaScript/TypeScript中的类,这是无关紧要的——从概念上讲,它们仍然是相关的类。有时你想一视同仁地对待所有产品,但衬衫和笔记本电脑都有特定的行为或数据。

有几种方法可以实现:

单表继承

单个";宽";表(或Keystone列表),其中包含所有子类所需的所有字段。然后,当您处理特定于子类的行为时,您只需忽略所有您不关心的属性。

听起来你已经考虑过了,并排除了它:

因此,仅仅为产品制定一个架构是行不通的。因为为笔记本电脑填写一张显示尺寸、颜色和材料的表格是没有意义的。在输入新t恤时,看到CPU/内存/屏幕大小字段也没有意义。

是的,它可以适用于少数子类,或者子类之间的差异很小,但通常不能很好地扩展。除了在Keystone Admin UI中有一个超级膨胀的表单之外,在命名不同的字段时也会发生冲突。假设你想在衬衫color字段中添加一个用于笔记本电脑的color字段,并有不同的选项,你会遇到冲突。所以,也许可以用子类作为所有字段的前缀,以避免冲突(所以shirt_colorlaptop_color)?但是,您想要添加一个HoodieProduct确实使用相同的衬衫颜色。。。它很快变得复杂起来。

多表继承

父类的一个表/列表,包括公共字段,然后是每个子类的另一个表或列表。

这是你提到的第二个选项:

我想我可以为每个类别创建单独的模式。因此,每个类别的唯一字段没有显示在其他类别中。然后,我将创建一个与每个类别都有关系的主产品模式文件。但对我来说,这听起来过于复杂,而且不可扩展,因为可能会添加新的类别。

它比";"单表";方法,但更具可扩展性&您不太受子类数量和它们之间差异的限制。在Keystone中,您的管理UI体验仍然有点笨拙,因为您需要点击其他列表中的特定类别字段(除非您自定义管理UI以避免这种情况)。数据库还需要做更多的工作,因为它需要在两个表中查找才能获得一个产品。尽管如此,有一个productId和所有公共字段放在一个地方还是很好的。

混凝土表继承

在这个解决方案中,您可以将子类完全拆分,将公共模式复制到每个子类中,如下所示。在您的示例中,这意味着有LaptopProductShirtProduct列表,但没有共享的Product列表。

这可以说会给你最好的";开箱即用";管理员UI体验(因为每个产品类别都有一个单独的创建/编辑表单,带有正确的字段),但它会让的许多其他事情变得更加困难,因为它阻止你真正使用";产品";在一般意义上。例如,像Orders这样的列表现在需要分别链接到每个产品类别。如果你使用CUID作为主键(Keystone中的默认值),你的产品ID仍然是唯一的,但无论你引用哪个,你都需要记录它的类别,这样你就知道该在哪个列表中查找它。


这些都是这个问题的经典解决方案。不幸的是,Keystone没有一个简单的开箱即用的解决方案来完全解决这个难题,但它确实为您提供了一些其他的解决方法:

Json油田

如果您同意特定类别的字段有点松散,您可以使用Json字段。开箱即用,它将作为文本区域显示在管理UI中,但仍然可以根据所选的产品类别添加验证。因此,例如,当创建一个新的衬衫产品时,您可以使用钩子将字段内容默认为:

{
"size": "M",
"color": "white",
"material": "cotton"
}

这至少会让你在编辑时有一点工作的结构。

自定义字段

在上面的基础上,Keystone允许您创建自定义字段类型,这些类型可以在这里做得很好。例如,您可以扩展Json字段类型,但可以自定义它在Admin UI中的呈现方式,以便为不同的选项使用实际字段。在这种情况下;普通的";product字段和category字段仍将在主列表模式中,那么所有特定于类别的内容都将由字段类型实现。

今天,这可能是您所概述的用例的最佳解决方案

未来科技

Keystone团队正在研究一种方法,将Json字段的灵活性与graphQL类型相结合,以在Admin UI中验证和强制执行特定的结构和生成的表单。它在这里被引用,并在路线图中被提及:

嵌套字段

有时,您需要管理嵌套和/或重复结构中的数据。我们正在研究一种在模式中定义这些字段的方法,并将它们存储为数据库中的JSON字段。

目前没有ETA,但如果你感兴趣,请在GitHub上关注该项目,以获得发布通知。

最新更新