您原始模型的问题在于,这三个类别并没有通过引用以任何方式相互关联。
我猜您想要的是这样的内容。
public abstract class CarModel
{
int id;
string manufacturer;
string name;
List<CarAttributeType> attributes;
}
public class CarModelAttribute
{
int id;
string name;
CodifiedValue value;
int dataOrder;
bool mandatory;
}
public class CodifiedValue
{
int id;
int code;
string description;
}
public class Car
{
int id;
string registration;
int mileage;
CarModel model;
public Car(registration, mileage, model) {...}
}
在你的代码中某个位置,你想要存储静态汽车模型
readonly CarModel toyotaPrius = new CarModel("Toyota", "Prius", priusAttributes)
Car myToyota = new Car("DF01 1DXM", 1000, toyotaPrius)
如果你正在使用ORM,这意味着你将获取加载的子项(如果它们通常是1:1的话,默认情况下,如果它们是0:n,那么可能是懒加载)。
这可能会导致大量的对象图被加载并编组成JSON/XML。
实际上,您希望您的编组程序仅创建解耦的引用,例如HATEOS样式。
GET /myApp/car/3 (where 3 is the ID of my Prius), which would get you
"Car"
{
"url" : "/myApp/car/3",
"id" : 3,
"registration" : "DF01 1DXM",
"mileage": 1000,
"model" : "/myApp/carModel/54", // where 54 maps back to the Id for the Toyota Prius
}
如果您想查看模型的详细信息,可以执行GET /myApp/carModel/54。
这意味着您的GET/PUT/POST等不需要填充或操作一个大的对象图。如果您希望检索整个图形,可以使用捆绑概念(我自己的术语)
GET /myApp/car/3/bundle
将返回
"Bundle"
{
"Car"
{
"url" : "/myApp/car/3",
"id" : 3,
"registration" : "DF01 1DXM",
"mileage": 1000,
"model" : "/myApp/carModel/54",
},
"CarModel"
{
"url" : "/myApp/carModel/54",
"id" : 54,
"manufacturer" : "Toyota",
"model": "Prius,
} // and so on and so forth for attributes
}
然后解析结果以查找链接,减少交互,同时保持完全平坦的图形。
对于0..*列表,您可以使用ListContainer(我自己的术语)。
"ListContainer"
{
"url" : "/myApp/car/3/attribute"
"ItemType": "CarModelAttribute",
"Count": 2,
"Items" : [
{attribute JSON goes here}, {attribute JSON goes here}
]
}
对于 URL 格式正确的列表获取请求,始终返回 200,并包含一个空列表。
"ListContainer"
{
"url" : "/myApp/car/3/attribute"
"ItemType": "CarModelAttribute",
"Count": 0,
"Items" : [],
"emptyReason" : "This Car Model has no attributes"
}
不要忘记使用良好的状态码,404表示未找到,410(我想是这样,请检查维基百科)表示删除的实体等。在POST / PUT验证失败(无法处理的实体)的情况下使用适当的代码。
注意使用部分
/myApp/car/3/attribute
对于Car id=3而言,易于阅读的方式是获取其所有属性(这将返回1,2,3,4四个属性)。
如果将来想要使用快捷方式,可以使用“/myApp/attribute/4”(如果ID是唯一的)。
如果想要提交整个汽车,需要提交一个bundle,所有的IDs都为空(除非你想支持客户端ID生成,通常是基于GUID,不要使用序列!)。
如果想要同时提交多辆汽车,请使用Transaction(确保你的端点被注释为transactional),例如:
POST /myApp/car/transaction
一个Transaction资源看起来像是一个捆绑包,但它会有一系列的捆绑包。
"transaction"
{
bundle : [ ]
}
一个 bundle 包含汽车及其属性,另一个可能包含车型信息(这样如果您想要 10 辆丰田普锐斯,就不会重复车型信息)。
希望这有所帮助。
一些澄清:
“1. 为什么 Car 实体的第一个属性是 url?”
通常将其视为“良好实践”,包含一个自我引用,可使您可以可幂等地返回您拥有的资源。
想象一下,如果有人保存了 JSON,您并不总是知道它来自哪里,但有了嵌入式 URL,您就知道了!
“2. 对于 Bundle,您写到:“然后解析结果以查找链接,在保持完全平坦的图形的同时减少聊天。” -> 如果所有东西都在那里,我该怎么处理链接?3. 关于第二个问题:图形是平面的。为什么不是‘包容’?”
您有一个平面图,以避免循环引用/深度对象。
想象一下,如果车上一次又一次地使用相同的零件,我们将说轮子,它有 20KB 的属性。
您可以 a) 每次重复相同的 20KB 属性,或者 b) 在对象中放置指向 /carPart/3 的链接,然后在捆绑包中仅具有完整对象一次。
更费力的解析,但避免了过度嵌套和重复。
此外,作为一个额外的奖励,您可以选择不包含子对象(如果您不想要捆绑版本),因此您的资源仅具有链接,这非常轻量级。这样,如果客户端需要更多信息,他们只需获取它。
“4. ListContainer 是常见实践吗?尽管使人感到困惑。”
我看过几个 REST 实现都使用了类似的内容,这是一种很好的方式,将列表与类型信息和告诉客户端列表可能为空的能力进行包装。
我的经验法则是,如果是直接的 ID 引用,例如 /car/3,则返回确定的 1 匹配项或状态代码(即您几乎可以肯定该项目存在)。如果您正在“查找”某些东西,例如
/car?manufacturer="Toyota"&model="Prius",那么您期望获得一个 0..* 的列表容器,即使实际上,它可能是 0..1。
“5.‘被注释为 transactional 的终点’?事务和 REST 听起来很糟糕,您能解释一下吗?我理解不重复模型信息。”
事务很棘手,但即使在 REST 中,有时也必须做一些原子性的事情。这给了您这种能力。我不喜欢使用它,但有时是不可避免的。