I am trying to find a proper type for my IDs in a Go program designed using Uncle Bob Martin's "Clean Architecture".
type UserID ...
type User struct {
ID UserID
Username string
...
}
type UserRepository interface {
FindByID(id UserID) (*User, error)
...
}
I am following Uncle Bob Martin's "Clean Architecture", where the code is organized as a set of layers (from outside-in: infrastructure, interfaces, usecases, and domain). One of the principles is the Dependency Rule: source code dependencies can only point inwards.
My User
type is part of the domain layer and so the ID
type cannot be dependent on the database chosen for the UserRepository
; if I am using MongoDB, the ID might be an ObjectId
(string
), while in PostgreSQL, I might use an integer. The User
type in the domain layer cannot know what the implementing type will be.
Through dependency injection a real type (e.g. MongoUserRepository
) will implement the UserRepository
interface and provide the FindByID
method. Since this MongoUserRepository
will be defined in the interfaces or infrastructure layer, it can depend on the definition of UserRepository
in the (more inward) domain layer.
I considered using
type UserID interface{}
but then the compiler will not be very helpful if code in one of the outer layer tries to assign in incorrect implementation type.
I want to have the interfaces layer or infrastructure layer, where the database is specified, determine and require the specific type for UserID
, but I cannot have the domain layer code import that information, because that will violate the dependency rule.
I also considered (and am currently using)
type UserID interface {
String() string
}
but that assumes knowledge that the database will use strings for its IDs (I am using MongoDB with its ObjectId
-- a type synonym for string
).
How can I handle this problem in an idiomatic fashion while allowing the compiler to provide maximum type safety and not violate the dependency rule?