Clean Architecture:编写应用程序业务规则时如何降低复杂度?
Clean Architecture: How to reduce complexity when writing application business rules?
假设我们有以下 "Create User" 场景:
- 用户可以使用 Facebook、Google+ 或 LinkedIn 注册该应用程序;
- 后端应该检索一些基本的配置文件信息以便注册用户(电子邮件、名字和姓氏);
- 用户注册了 "client Id"(只是增加了业务规则的复杂性);
- 完成注册过程后,应将数据发送到通知主题。
我可以想象一个具有以下结构的创建用户请求:
{
"clientId": "someClientId",
"authProvider": "FACEBOOK | GOOGLE | LINKEDIN",
"accessToken": "someAccessToken"
}
因此,考虑 registration/validation 流程,我们将拥有:
- 检查创建用户请求是否有效;
- 检查clientId是否有效;
- 尝试从社交网络检索个人资料信息api;
- 检查是否填写了所有必需的个人资料信息;
- 检查用户是否存在于数据库中;
- 注册用户;
- 发送数据到通知主题;
- 将数据传递给演示者。
直接跳到用例,我们会有一个像这样的构造函数:
CreateUserUseCase(
ApplicationClientGateway applicationClientGateway,
SocialNetworkGateway socialNetworkGateway,
UserGateway userGateway,
NotificationGateway notificationGateway,
Presenter presenter
)
和一个执行方法:
execute(CreateUserRequest request)
// validates the payload
// something like
if (request == null)
presenter.setError(someError);
// validates the clientId
applicationClientGateway.findById(request.getClientId())
// retrieves the profile information
// how to inject dinamically the implementation for
// Facebook, Google or LinkeIn based on a request parameter?
profile = socialNetworkGateway.findByAccessToken(request.getAccessToken());
// checks if the user exists
userGateway.findByEmailAndAuthProvider(profile.getEmail(), request.getAuthProvider());
//register the user
userGateway.insert(user);
//sends the notification
notificationGateway.send(user);
// sets the result
presenter.setResult(user);
现在,我们有一个带有很多参数(代码味道?)的构造函数,并且在执行方法中至少有 5 个验证步骤。
这似乎违反了 SRP,那么,我们如何分解这段代码以降低交互器的复杂性?
首先,让我们分几个小步骤打破它:
1) 与演示者有关,您似乎有兴趣为工作流程提供一些输出,对吗?假设那样,也许 return 你想从用例中得到什么并处理上面的这一层会更好。 (构造函数中的 -1 个参数)
2) 就像其他答案所说的那样,看起来您的用例现在有很多责任。我会建议你在多个用例中打破它。
类似于:
... Your first gateway (API)
..... ValidateClientId.execute();
..... profile = RetrieveProfile.execute();
..... InsertUser.execute(...)
3.1) 与基于正确的社交网络注入正确的 bean 有关,您可以在网关内部处理此逻辑,而不是在调用它之前。请记住,一个网关可以调用另一个网关(它们在同一层)。所以,我建议你使用类似的东西。
在用户案例中 -> socialNetworkGateway.findByAccessToken(...)
在网关内,您可以执行 "switch" 并调用 FacebookGateway、GoogleGateway 等
SRP 并没有说你必须有小方法或只有几个构造函数参数。 SRP 表示 "there should be only a single reason to change the code".
据我所知,您明确实施了注册新用户所需的 "business logic sequence"。尽管这可能需要一些 "external services" and/or 存储库,但仍然只有一个原因需要更改此代码的逻辑:如果逻辑 "how to register a user" 更改。
从这个角度来看,您没有违反 SRP。
根据鲍勃大叔在"control flow"(https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)上的图片,通过主持人也是完全正确的。
如果您仍然觉得需要减少用例的依赖性 class 我建议您研究 "unit of work" 模式并检查组合某些依赖性是否有意义。
有关 "What is a use case in Clean Architecture" 的更多详细信息,您可以在我的博客系列中找到:http://www.plainionist.net/Implementing-Clean-Architecture-UseCases/
假设我们有以下 "Create User" 场景:
- 用户可以使用 Facebook、Google+ 或 LinkedIn 注册该应用程序;
- 后端应该检索一些基本的配置文件信息以便注册用户(电子邮件、名字和姓氏);
- 用户注册了 "client Id"(只是增加了业务规则的复杂性);
- 完成注册过程后,应将数据发送到通知主题。
我可以想象一个具有以下结构的创建用户请求:
{
"clientId": "someClientId",
"authProvider": "FACEBOOK | GOOGLE | LINKEDIN",
"accessToken": "someAccessToken"
}
因此,考虑 registration/validation 流程,我们将拥有:
- 检查创建用户请求是否有效;
- 检查clientId是否有效;
- 尝试从社交网络检索个人资料信息api;
- 检查是否填写了所有必需的个人资料信息;
- 检查用户是否存在于数据库中;
- 注册用户;
- 发送数据到通知主题;
- 将数据传递给演示者。
直接跳到用例,我们会有一个像这样的构造函数:
CreateUserUseCase(
ApplicationClientGateway applicationClientGateway,
SocialNetworkGateway socialNetworkGateway,
UserGateway userGateway,
NotificationGateway notificationGateway,
Presenter presenter
)
和一个执行方法:
execute(CreateUserRequest request)
// validates the payload
// something like
if (request == null)
presenter.setError(someError);
// validates the clientId
applicationClientGateway.findById(request.getClientId())
// retrieves the profile information
// how to inject dinamically the implementation for
// Facebook, Google or LinkeIn based on a request parameter?
profile = socialNetworkGateway.findByAccessToken(request.getAccessToken());
// checks if the user exists
userGateway.findByEmailAndAuthProvider(profile.getEmail(), request.getAuthProvider());
//register the user
userGateway.insert(user);
//sends the notification
notificationGateway.send(user);
// sets the result
presenter.setResult(user);
现在,我们有一个带有很多参数(代码味道?)的构造函数,并且在执行方法中至少有 5 个验证步骤。
这似乎违反了 SRP,那么,我们如何分解这段代码以降低交互器的复杂性?
首先,让我们分几个小步骤打破它:
1) 与演示者有关,您似乎有兴趣为工作流程提供一些输出,对吗?假设那样,也许 return 你想从用例中得到什么并处理上面的这一层会更好。 (构造函数中的 -1 个参数)
2) 就像其他答案所说的那样,看起来您的用例现在有很多责任。我会建议你在多个用例中打破它。 类似于:
... Your first gateway (API)
..... ValidateClientId.execute();
..... profile = RetrieveProfile.execute();
..... InsertUser.execute(...)
3.1) 与基于正确的社交网络注入正确的 bean 有关,您可以在网关内部处理此逻辑,而不是在调用它之前。请记住,一个网关可以调用另一个网关(它们在同一层)。所以,我建议你使用类似的东西。
在用户案例中 -> socialNetworkGateway.findByAccessToken(...) 在网关内,您可以执行 "switch" 并调用 FacebookGateway、GoogleGateway 等
SRP 并没有说你必须有小方法或只有几个构造函数参数。 SRP 表示 "there should be only a single reason to change the code".
据我所知,您明确实施了注册新用户所需的 "business logic sequence"。尽管这可能需要一些 "external services" and/or 存储库,但仍然只有一个原因需要更改此代码的逻辑:如果逻辑 "how to register a user" 更改。
从这个角度来看,您没有违反 SRP。
根据鲍勃大叔在"control flow"(https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)上的图片,通过主持人也是完全正确的。
如果您仍然觉得需要减少用例的依赖性 class 我建议您研究 "unit of work" 模式并检查组合某些依赖性是否有意义。
有关 "What is a use case in Clean Architecture" 的更多详细信息,您可以在我的博客系列中找到:http://www.plainionist.net/Implementing-Clean-Architecture-UseCases/