Python对象层次结构与REST资源?
我现在在我的Python应用程序(使用Twisted)中遇到了一个“架构”问题,这个应用程序使用了REST API,我想听听大家的意见。
警告!接下来会有一段比较长的内容!
假设我们有以下的对象层次结构:
class Device(object):
def __init__():
self._driver=Driver()
self._status=Status()
self._tasks=TaskManager()
def __getattr__(self, attr_name):
if hasattr(self._tasks, attr_name):
return getattr(self._tasks, attr_name)
else:
raise AttributeError(attr_name)
class Driver(object):
def __init__(self):
self._status=DriverStatus()
def connect(self):
"""some code here"""
def disconnect(self):
"""some code here"""
class DriverStatus(object):
def __init__(self):
self._isConnected=False
self._isPluggedIn=False
我还有一个相当复杂的对象层次结构(上面的元素只是其中的一部分)。所以,现在在REST API中,我得到了以下资源(我知道,REST并不是关于URL层次结构的,而是关于媒体类型的,但为了简单起见,我这样说):
/rest/environments
/rest/environments/{id}
/rest/environments/{id}/devices/
/rest/environments/{id}/devices/{deviceId}
/rest/environments/{id}/devices/{deviceId}/driver
/rest/environments/{id}/devices/{deviceId}/driver/driverstatus
几个月前,我从一个“脏”的SOAP类型API切换到了REST,但我开始对如何处理似乎增加的复杂性感到不确定:
REST资源/媒体类型的增加:例如,现在我不仅仅有一个设备资源,还有这些资源:
- 设备
- 设备状态
- 驱动程序
- 驱动程序状态
虽然从RESTful的角度来看这些都是合理的,但有很多子资源,每个都映射到一个单独的Python类,这正常吗?
将方法丰富的应用核心映射到RESTful API:在REST中,资源应该是名词,而不是动词:有没有好的规则或建议,可以从一组方法智能地定义一组资源?(我找到的最全面的例子似乎是这篇文章)
API逻辑影响应用结构:应用程序的API逻辑是否至少部分上应该指导其内部逻辑,还是将关注点分离是好做法?也就是说,我是否应该有一个中间层的“资源”对象,负责与应用核心进行通信,但不与核心的类一一对应?
如何以RESTful的方式正确处理以下情况:我需要能够在客户端显示可用的驱动程序类型列表(即类名,而不是驱动程序实例):这是否意味着要创建另一个资源,比如“DriverTypes”?
这些问题比较复杂,所以感谢你的耐心,任何指点、反馈和批评都非常欢迎!
对S.Lott:
我所说的“资源过于分散”是指很多不同的子资源,基本上仍然适用于同一个服务器端实体。
关于“连接”:那会是“驱动程序状态”资源的一个修改版本吗?我认为连接是始终存在的,因此使用“PUT”,但考虑到“PUT”应该是幂等的,这样做会不会不好?
你说得对,“停止编码并重新思考”,这就是我问这些问题并把事情写下来,以便更好地了解的原因。
-问题是,现在基本的“现实世界对象”,正如你所说的,作为REST资源/资源集合对我来说是有意义的,并且它们通过POST、GET、UPDATE、DELETE被正确操作,但我很难理解REST方法对于那些我本能上不认为是“资源”的东西。
2 个回答
你可以用一个 Site
对象来表示路径部分 /rest
,但是路径中的 environments
必须是一个 Resource
。接下来,你需要在 environments
的 render_*
方法中自己处理层级关系。你会得到一个 request
对象,它有一个 postpath
属性,里面包含了路径的其余部分(也就是在 /rest/environments
之后的部分)。你需要从中解析出 id
,判断路径中是否包含 devices
,如果有的话,就把剩下的路径(和请求)传递给你的设备集合。很遗憾,Twisted 不会为你处理这个决定。
规则一:REST是关于对象的,而不是方法。
REST的“资源”变得太分散了。
错!永远都是错的。REST资源是独立的,不能说它们“太”分散了。
现在我不再只有一个设备资源,而是有这些资源:
设备 设备状态 驱动程序 驱动程序状态
虽然从[RESTful]的角度来看这些都很合理,但有这么多子资源,每个都对应一个单独的Python类,这正常吗?
其实,这些并不合理,所以你才会问这个问题。
设备就是一个东西。比如说:/rest/environments/{id}/devices/{deviceId}
它有状态。你应该考虑把状态和设备信息一起提供,作为一个描述设备的综合文档。
仅仅因为你的关系数据库是规范化的,并不意味着你的RESTful对象也需要和数据库一样严格规范化。虽然这样做更简单(而且很多框架让这变得非常简单),但这可能并不有意义。
考虑到连接总是存在,因此使用“PUT”,但考虑到“PUT”应该是幂等的,这样做会不会有问题?
连接并不是总是存在的,它们可能会时有时无。
虽然关系数据库可能有一个多对多的关联表,你可以对其进行更新,但这是一种特殊情况,在数据库管理员的世界之外并没有太大意义。
两个RESTful事物之间的连接很少是一个单独的东西。它是每个RESTful事物的一个属性。
这个“连接”到底是什么并不清楚。你只是模糊地提到它,但没有提供细节。
缺乏任何可用的事实,我猜测你是在连接设备和驱动程序,并且有某种[Device]<-[Driver Status]->[Driver
]的关系。设备和驱动程序之间的连接可以是一个单独的RESTful资源。
它也可以很容易地是设备或驱动程序的一个属性,而不需要一个单独、可见的RESTful资源。
[再说一次,一些框架像Django-Piston让暴露底层类变得很简单,但这并不总是合适的。]
有没有好的规则/建议来智能地从一组方法定义一组资源?
有。不要这样做。资源不是方法。基本上就是这样。
如果你有很多方法——除了CRUD之外——那么你可能在数据模型上有问题。你可能在关系模型中表达的事物类别太少,而状态更新的事物太多。
有状态的对象并不本质上是坏事,但需要认真审视。在某些情况下,改变对象状态的PUT请求可能应该是一个POST请求,用于添加对象的历史记录。“当前”状态是最后一个POST的内容。
还有。
你不必把每个资源都简单化为一类事物。你可以有资源是集合。你可以向一个综合(实际上是一个外观)“资源”POST一个相当复杂的文档。这个复杂的文档可以在数据库中隐含多个CRUD操作。
你正在偏离简单的RESTful。你的问题仍然故意模糊。“方法丰富的应用核心”并没有太大意义。没有具体的例子,很难想象。
API逻辑影响应用结构
如果这些是不同的,你可能在制造不必要的、没有价值的复杂性。
应用分离关注点是好习惯吗?
当然。为什么要问?
很多问题似乎源于我对如何将一个方法丰富的API映射到RESTful API的困惑,资源应该是名词,而不是动词:那么,什么时候考虑一个元素是REST“资源”是明智的?
资源是由你的问题领域定义的。通常是一些具体的东西。方法(在“方法丰富的API”中)通常无关紧要。它们是CRUD(创建、检索、更新、删除)操作。如果你有一些不是本质上CRUD的东西,你就得停止编码。停止写代码,重新思考API,使其更像CRUD。
CRUD - 创建-检索-更新-删除对应REST的POST-GET-PUT-DELETE。如果你无法将你的问题领域重新表述为这些术语,就停止编码。停止编码,直到你理解CRUD规则。
我需要能够在客户端显示可用的驱动程序类型列表(即类名,而不是驱动程序实例):这是否意味着要创建另一个资源,比如“DriverTypes”?
没错。它们已经是你问题领域的一部分。你已经定义了这个类。你只是通过REST将其公开。
关键在于,问题领域有现实世界的对象。你有类的定义。它们是具体的东西。REST传递这些具体事物的状态。
你的软件可能有一些无形的东西,比如“关联”或“链接”或“连接”,这些都是软件解决方案的一部分。这些无关紧要。它们是实现细节,而不是现实世界的事物。
一个“关联”总是可以从两个现实世界的RESTful资源中看到。一个资源可能有一个外键引用,允许客户端进行RESTful获取另一个相关对象。或者一个资源可能有一组其他相关对象,单个GET请求可以检索一个对象和一组相关对象。
无论如何,现实世界的RESTful资源是可用的。关系只是隐含的。即使它是一个物理的多对多数据库表——这并不意味着它必须被公开。[再说一次,一些框架让暴露一切变得非常简单,但这并不总是好事。]