在 Clean Architecture 中设计实体的最佳实践是什么?
What is the best practice for designing entities in Clean Architecture?
我正在尝试使用 Kotlin 实现简洁的架构。该过程的流程将是:
usecase --> get rowresult from DB --> map rowresult to entity --> entity used by the usecase to check business rules
代码示例:
UserTable
------------------
id (varchar)
email (varchar)
password (varchar)
gender (varchar)
phone (varchar)
anotherAttribute1
anotherAttribute2
.
anotherAttributeN
class UserEntity {
val id: String,
val email: String,
val password: String,
//Business rules
fun isUserAllowedToLogin(): Boolean {
//validate password
}
}
interface UserDataStore {
fun getUser(email: String): User
}
class UserDataStoreImplementation {
fun getUser(email: String): User {
//query to DB
val resultRow = db.query("SELECT id, email, password from UserTable where email=${email}")
//map to UserEntity
val user: UserEntity = Mapper.toUserEntity(userResultRow)
return user
}
}
class LoginUseCase {
fun execute(emailInput: String, passwordInput: String): Boolean {
val user = UserDataStore().getUser(emailInput)
if (!user.isUserAllowedToLogin) {
//do something
}
return result
}
}
请注意,loginUseCase 使用的唯一属性是用户电子邮件和密码。
问题 1. 假设我有另一个 UseCase (GetUserFullDetailAndStaffDetail Usecase) 将使用更复杂的 User 属性,我应该为 GetUserFullDetailAndStaffDetail 用例使用相同的 UserEntity 吗?所以 UserEntity 将是:
class UserEntity {
val id: String,
val email: String,
val password: String,
val gender: String,
val phone: String,
//more attributes
.
.
//more complex object
val Staff: Staff
fun isUserAllowedToLogin(): Boolean {
//validate password
}
fun checkStaffStatus(): Boolean {
//do something
}
}
class UserDataStoreImplementation {
fun getUser(email: String): User {
//query from DB which will have a lot of attributes
val resultRow = db.query("SELECT * from UserTable where email=${email}")
//map to UserEntity
val user: UserEntity = Mapper.toUserEntity(userResultRow)
}
}
如果我使用不同的实体,它将违反 DRY 原则(UserDataStoreImplementation 中重复的 UserEntity 和重复的 getUser 方法),但是如果我对 GetUserFullDetailAndStaffDetail 用例使用相同的 UserEntity,则 UserDataStoreImplementation 中的 getUser 必须获得没有用的完整属性.
问题 2. UserDataStoreImplementation 中的 getUser 是否应该有不同的方法(一个将 return UserTable 中的部分属性用于 LoginUseCase,另一个将 return UserTable 中的完整属性用于 GetUserFullDetailAndStaffDetail UseCase)?
Question 1. Suppose if I have another UseCase (GetUserFullDetailAndStaffDetail Usecase) that will use more complex attribute of User, should I use the same UserEntity for GetUserFullDetailAndStaffDetail usecase?
该实体是域对象,LoginUser
与 UserDetail
不同。我们经常认为有一个User
。但是用户有不同的观点。您可以将它们视为一种角色。
public class LoginUser {
private String name;
private String email;
// ...
}
或 DetailUser
.
public class DetailUser {
private String name;
private String email;
private String phone;
private String gender;
// ...
}
如您所见,我省略了 id
。通常它是数据库详细信息而不是域 属性。但有时它是,例如比如客户编号。
If I use different entity it will violate DRY principle (duplicate UserEntity and duplicate getUser method in UserDataStoreImplementation), but if I use the same UserEntity for GetUserFullDetailAndStaffDetail usecase, the getUser in UserDataStoreImplementation for LoginUseCase must get full attribute which is useless.
不违反干货原则,因为你不重复自己。我同意我们必须摆脱重复的代码,但是 LoginUser
会因为不同的原因而改变 DetailUser
。因此它们不重复。这就是单一责任的意义所在。
它们看起来很相似,但相似只是重复的暗示。你必须问自己更多的问题,看看它们是否真的重复了。让我们考虑一下对登录用例的更改。也许只应显示名称。那么这两个实体将只有 1 属性 个共同点——名字。他们必须共有多少个属性才能复制?
如果您在实体中实现业务逻辑,您将意识到有些方法只会在两个用例之一中调用,并且这些方法仅使用属性的一个子集。然后你会发现这两个实体是不一样的
Question 2. Should there be different methods for getUser in UserDataStoreImplementation (one will return partial attribute in UserTable for LoginUseCase, the other will return full attribute in UserTable for GetUserFullDetailAndStaffDetail UseCase)?
我会说每个用例都应该定义它自己的存储库接口。这个接口应该只定义这个用例需要的方法。这是接口隔离原则的应用,它遵守单一职责原则。
就像你指出的那样,一种方法将 return 完整的属性,因为它服务于 GetUserFullDetailAndStaffDetail
用例。
如果您只对所有用例使用一个存储库接口,您很快就会意识到它会越来越大,直到它包含数十种方法。最后这一界面会变得混乱。有些方法相似,但不同,您将尝试找到 疯狂的名称来区分它们。
public interface UserRepository {
public User findSimpleUser();
public User findAllUserInfo();
public User findAllUserInfoForOrderProcess();
// ... maybe dozens more
}
实施 class 会很大。也许很多方法都是通过私有实用方法耦合的,所以对这个共享代码的更改会影响其他用例,等等。
分离接口是个好主意。也许您从不同的接口开始,但只有一个实现两者的实现。也许您想在情况变得更糟时分解实施。但是你已经有了分离的接口,你不必改变你的用例。
我正在尝试使用 Kotlin 实现简洁的架构。该过程的流程将是:
usecase --> get rowresult from DB --> map rowresult to entity --> entity used by the usecase to check business rules
代码示例:
UserTable
------------------
id (varchar)
email (varchar)
password (varchar)
gender (varchar)
phone (varchar)
anotherAttribute1
anotherAttribute2
.
anotherAttributeN
class UserEntity {
val id: String,
val email: String,
val password: String,
//Business rules
fun isUserAllowedToLogin(): Boolean {
//validate password
}
}
interface UserDataStore {
fun getUser(email: String): User
}
class UserDataStoreImplementation {
fun getUser(email: String): User {
//query to DB
val resultRow = db.query("SELECT id, email, password from UserTable where email=${email}")
//map to UserEntity
val user: UserEntity = Mapper.toUserEntity(userResultRow)
return user
}
}
class LoginUseCase {
fun execute(emailInput: String, passwordInput: String): Boolean {
val user = UserDataStore().getUser(emailInput)
if (!user.isUserAllowedToLogin) {
//do something
}
return result
}
}
请注意,loginUseCase 使用的唯一属性是用户电子邮件和密码。
问题 1. 假设我有另一个 UseCase (GetUserFullDetailAndStaffDetail Usecase) 将使用更复杂的 User 属性,我应该为 GetUserFullDetailAndStaffDetail 用例使用相同的 UserEntity 吗?所以 UserEntity 将是:
class UserEntity {
val id: String,
val email: String,
val password: String,
val gender: String,
val phone: String,
//more attributes
.
.
//more complex object
val Staff: Staff
fun isUserAllowedToLogin(): Boolean {
//validate password
}
fun checkStaffStatus(): Boolean {
//do something
}
}
class UserDataStoreImplementation {
fun getUser(email: String): User {
//query from DB which will have a lot of attributes
val resultRow = db.query("SELECT * from UserTable where email=${email}")
//map to UserEntity
val user: UserEntity = Mapper.toUserEntity(userResultRow)
}
}
如果我使用不同的实体,它将违反 DRY 原则(UserDataStoreImplementation 中重复的 UserEntity 和重复的 getUser 方法),但是如果我对 GetUserFullDetailAndStaffDetail 用例使用相同的 UserEntity,则 UserDataStoreImplementation 中的 getUser 必须获得没有用的完整属性.
问题 2. UserDataStoreImplementation 中的 getUser 是否应该有不同的方法(一个将 return UserTable 中的部分属性用于 LoginUseCase,另一个将 return UserTable 中的完整属性用于 GetUserFullDetailAndStaffDetail UseCase)?
Question 1. Suppose if I have another UseCase (GetUserFullDetailAndStaffDetail Usecase) that will use more complex attribute of User, should I use the same UserEntity for GetUserFullDetailAndStaffDetail usecase?
该实体是域对象,LoginUser
与 UserDetail
不同。我们经常认为有一个User
。但是用户有不同的观点。您可以将它们视为一种角色。
public class LoginUser {
private String name;
private String email;
// ...
}
或 DetailUser
.
public class DetailUser {
private String name;
private String email;
private String phone;
private String gender;
// ...
}
如您所见,我省略了 id
。通常它是数据库详细信息而不是域 属性。但有时它是,例如比如客户编号。
If I use different entity it will violate DRY principle (duplicate UserEntity and duplicate getUser method in UserDataStoreImplementation), but if I use the same UserEntity for GetUserFullDetailAndStaffDetail usecase, the getUser in UserDataStoreImplementation for LoginUseCase must get full attribute which is useless.
不违反干货原则,因为你不重复自己。我同意我们必须摆脱重复的代码,但是 LoginUser
会因为不同的原因而改变 DetailUser
。因此它们不重复。这就是单一责任的意义所在。
它们看起来很相似,但相似只是重复的暗示。你必须问自己更多的问题,看看它们是否真的重复了。让我们考虑一下对登录用例的更改。也许只应显示名称。那么这两个实体将只有 1 属性 个共同点——名字。他们必须共有多少个属性才能复制?
如果您在实体中实现业务逻辑,您将意识到有些方法只会在两个用例之一中调用,并且这些方法仅使用属性的一个子集。然后你会发现这两个实体是不一样的
Question 2. Should there be different methods for getUser in UserDataStoreImplementation (one will return partial attribute in UserTable for LoginUseCase, the other will return full attribute in UserTable for GetUserFullDetailAndStaffDetail UseCase)?
我会说每个用例都应该定义它自己的存储库接口。这个接口应该只定义这个用例需要的方法。这是接口隔离原则的应用,它遵守单一职责原则。
就像你指出的那样,一种方法将 return 完整的属性,因为它服务于 GetUserFullDetailAndStaffDetail
用例。
如果您只对所有用例使用一个存储库接口,您很快就会意识到它会越来越大,直到它包含数十种方法。最后这一界面会变得混乱。有些方法相似,但不同,您将尝试找到 疯狂的名称来区分它们。
public interface UserRepository {
public User findSimpleUser();
public User findAllUserInfo();
public User findAllUserInfoForOrderProcess();
// ... maybe dozens more
}
实施 class 会很大。也许很多方法都是通过私有实用方法耦合的,所以对这个共享代码的更改会影响其他用例,等等。
分离接口是个好主意。也许您从不同的接口开始,但只有一个实现两者的实现。也许您想在情况变得更糟时分解实施。但是你已经有了分离的接口,你不必改变你的用例。