" Clean Architecture"的通用ID类型去节目

时间:2016-07-06 12:15:56

标签: go clean-architecture dependency-rule

我正试图在使用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可能是ObjectIdstring),而在PostgreSQL中,我可能使用整数。域层中的User类型无法知道实现类型是什么。

通过依赖注入,真实类型(例如MongoUserRepository)将实现UserRepository接口并提供FindByID方法。由于此MongoUserRepository将在接口或基础架构层中定义,因此它可能取决于(更向内)域层中UserRepository的定义。

我考虑过使用

type UserID interface{}

但是如果外层之一的代码尝试分配不正确的实现类型,编译器将不会非常有用。

我希望在指定数据库的接口层或基础结构层确定并要求UserID的特定类型,但我不能让域层代码导入该信息,因为这会违反依赖规则。

我也考虑过(现在正在使用)

type UserID interface {
    String() string
}

但是假设知道数据库将使用字符串作为其ID(我使用MongoDB及其ObjectId - string的类型同义词。)

如何以惯用的方式处理此问题,同时允许编译器提供最大的类型安全性而不违反依赖性规则?

3 个答案:

答案 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)

我遇到了同样的问题,根据一些阅读,这就是我计划使用的东西。

  • 将ID生成保留在域之外。
  • 由于存储库处理了所有条目,因此存储库生成新的ID很有意义。
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/