我正试图在使用Uncle Bob Martin" Clean Architecture"设计的Go程序中为我的ID找到合适的类型。
type UserID ...
type User struct {
ID UserID
Username string
...
}
type UserRepository interface {
FindByID(id UserID) (*User, error)
...
}
我正在关注鲍勃·马丁叔叔" Clean Architecture",其中代码被组织为一组图层(来自外部:基础设施, interfaces , usecases 和 domain )。其中一个原则是依赖关系规则:源代码依赖关系只能指向内部。
我的User
类型是域图层的一部分,因此ID
类型不能依赖于为UserRepository
选择的数据库;如果我使用MongoDB,ID可能是ObjectId
(string
),而在PostgreSQL中,我可能使用整数。域层中的User
类型无法知道实现类型是什么。
通过依赖注入,真实类型(例如MongoUserRepository
)将实现UserRepository
接口并提供FindByID
方法。由于此MongoUserRepository
将在接口或基础架构层中定义,因此它可能取决于(更向内)域层中UserRepository
的定义。
我考虑过使用
type UserID interface{}
但是如果外层之一的代码尝试分配不正确的实现类型,编译器将不会非常有用。
我希望在指定数据库的接口层或基础结构层确定并要求UserID
的特定类型,但我不能让域层代码导入该信息,因为这会违反依赖规则。
我也考虑过(现在正在使用)
type UserID interface {
String() string
}
但是假设知道数据库将使用字符串作为其ID(我使用MongoDB及其ObjectId
- string
的类型同义词。)
如何以惯用的方式处理此问题,同时允许编译器提供最大的类型安全性而不违反依赖性规则?
答案 0 :(得分:1)
也许你可以使用这样的东西:
type UserID interface {
GetValue() string
SetValue(string)
}
然后你假设你总是传递并获取字符串作为ID(它可以是PgSQL和其他RDBMS的整数ID的字符串化版本),并且您为每个数据库类型实现UserID:
type PgUserID struct {
value int
}
func (id *PgUserID) GetValue() string {
return strconv.Itoa(id.value)
}
func (id *PgUserID) SetValue(val string){
id.value = strconv.Atoi(val)
}
type MongoUserID struct {
value string
}
func (id *MongoUserID) GetValue() string {
return value
}
func (id *MongoUserID) SetValue(val string){
id.value = val
}
我想知道这是否能实现您想要实现的目标,但是在UserID中隐藏字符串转换可能更优雅吗?
答案 1 :(得分:0)
User
真的需要与身份相结合吗?
我不知道这是辉煌还是愚蠢,但我已经停止在数据类中添加身份属性,并且仅将其用作访问信息的方式,就像使用地图或字典一样。< / p>
这允许我选择一种类型的身份(UUID),当我测试我的用例(并为存储库接口编写模拟实现)时很方便,而另一种类型的身份(Integer)在我沟通时很方便与数据库。
无论哪种方式,我的数据类保持不变,我发现它非常方便。
(在某些时候,我确实在我的数据类中将身份保持为通用,但我认为某些东西 - 也许是equals和hashCode的实现,不记得了 - 迫使我决定身份的类型)
我主要用Java编写代码并且对Go没有任何了解,但在Java中我会使用:
public class User {
private final String username;
...
}
public interface UserRepository<K> {
public User findById(K identity)
}
并且依赖于UserRepository的我的用例测试将使用带有UUID的UserRepository的模拟实现作为标识的类型:
public class UserRepositoryMock implements UserRepository<UUID> {
public User findById(UUID identity) {
...
}
}
但我对实际数据库的UserRepository实现将使用Integer作为标识的类型:
public class UserSQLRepository implements UserRepository<Integer> {
public User findById(Integer identity) {
...
}
}
答案 2 :(得分:0)
我遇到了同样的问题,根据一些阅读,这就是我计划使用的东西。
class UserId:
# Hides what kind of unique-id is being used and lets you change it without affecting the domain logic.
pass
class User:
uid: UserId
name: str
class UserRepository:
def next_id() -> UserId:
# Can use UUID generator
# Or database based sequence number
pass
class UserUsecase:
def __init__(self, repository):
self.repository = repository
def create(self, name):
user_id = self.repository.next_id()
user = User(user_id, name)
self.repository.save(user)
return user
参考: https://matthiasnoback.nl/2018/05/when-and-where-to-determine-the-id-of-an-entity/