当我想要更新非简单域对象时,我在组织代码时遇到问题。问题是要分离控制器和服务层的职责。
更明确地说,假设我们有一个域类Client,它依赖于其他域类,如Address等。
在视图中有一个用于编辑某些Clients属性的gsp,包括一些嵌套属性,例如Address上的street。
当我想更新这些字段时,我在Controller上调用update方法(在本例中是ClientController)。
我喜欢在验证时来自域类错误的功能。就像我在控制器中写道
Client client = Client.get(params.id)
client.properties = params
client.validate()
如果客户端现在有错误,很容易在编辑视图中显示它们。
但是,我认为更新,保存和从数据库中获取客户端(Client.get(theId))应该由服务层处理。在我的情况下,我必须在更新客户端之前更新或创建其他域对象(如地址)。
所以我的一个问题是服务层的API应该如何?
public ... updateClient(…)
在文献中,他们有一个简单的例子来更新一个人的年龄。因此,他们的API由人的身份和新时代组成。但是,在我的情况下,我从视图中看到大约十个参数,它们只是客户端所有属性的一个子集,我不知道哪一个已经改变了。
我如何结合这些?不同层面对更新有哪些责任?关于更新,服务层的API应该如何?
如果在某个地方有一个很好的参考实现,我会很乐意研究它。很多时候,服务层不幸被完全或部分忽略。
答案 0 :(得分:5)
这个难题的缺失部分是command objects。这些类表示API与服务的合约,并使您能够为视图和验证创建具体的类。让我们来看一个例子。
鉴于Client
的域类,Address
和几个Phone
个实例,您的服务层可能如下所示:
...
class ClientService {
def updateClient(ClientUpdateCommand cmd) {
..
}
}
...
虽然ClientUpdateCommand看起来像这样:
@grails.validation.Validateable
class ClientUpdateCommand {
Long id
String name
List<PhoneUpdateCommand> phoneNumbers = []
AddressUpdateCommand address = new AddressUpdateCommand()
...
static constraints {
id(nullable: false)
name(nullable: false, blank: false, size:1..50)
...
}
...
}
您将注意到此命令对象由其他命令对象组成,并具有验证约束。您似乎在这里复制域类,但我发现应用程序越复杂,域类和命令对象之间就会出现更多差异。
接下来是在控制器和视图中使用命令对象。控制器方法可能如下所示:
Class ClientController {
...
def clientService
...
def edit(ClientUpdateCommand cmd) {
...
cmd = clientService.load(cmd.id)
Map model = [client: cmd]
render(view: 'edit', model: model)
}
def update(ClientUpdateCommand cmd) {
Map model = [client: cmd]
if (cmd.hasErrors() {
render(view: 'edit', model: model]
return
} else {
clientService.update(cmd)
...
}
...
}
}
我从控制器中留下了很多东西,因为我不想让你了解细节,而是展示命令对象如何替换域实例。在某些方面,它需要更多的工作,但它会使您完全远离操纵域类并将其委托给您创建的服务。您还会注意到命令对象替换了视图模型的域类实例。我不打算给你任何关于GSP的例子,因为在使用这样的命令对象时他们真的没什么变化。
我确信可能会有关于这个主题的书籍的整个章节,但希望这会给你一些见解,你可以看到你的问题的答案是:命令对象。