Reputation: 1039
I want to make models for my framework, written in go, and I'm not sure how to compose them in a way that shares the common database interaction methods: save, update, delete.
I would normally do this by creating a Model abstract parent class to all concrete models, but Go doesn't have inheritance. You're supposed to use embedding and composition instead, but I don't see how I can embed a model class and have it save the data of the class holding it.
I see the other option, of creating a model class that embeds a concrete model type within it, but I don't really see an interface that would apply to all the models unless it was empty. That brings with it the insecurity that anything can be considered a model.
What do?
Upvotes: 2
Views: 3665
Reputation: 20205
In my projects I do something like this:
type Storable interface {
// called after unmarshalling from the database
Init() error
// called when an object is being deleted
// this is useful if the object needs to delete other objects,
// change state on a remote server, etc.
Destroy() error
// called after Init, helps separate initialization from
// sanity checks (useful to detect errors before using a potentially
// invalid object)
Validate() error
// type of this object, stored in the database in `Save` and `Update`
// so it can be read out in `Get`
Type() string
}
If you're working with an SQL database, you could do something like this:
type Schema map[string]reflect.Type
type SQLStorable interface {
Storable
Schema() Schema
}
Then in the database, I have functions like this:
func Get(id string) (Storable, error)
func Save(Storable) error
func Update(id string, Storable) error
func Delete(id string) error
// register a type with the database (corresponds to the Type() in Storable)
func Register(typ string, reflect.Type)
I keep a cache of objects in the database: map[string]Storable
. This allows me to implement caching logic to reduce lookup times (don't need to reconstruct objects each time it's read from the database).
In my project, I have lots of packages that need to talk with objects from other packages. Since managing dependency chains would be a nightmare, I've set up a messaging system that uses the database:
type Message map[string]interface{}
func Send(id string, Message)
And I've added a Receive
function to Storable that takes a Message
and returns an error. This has reduced many headaches so far and has lead to a more pluggable design.
I'm not sure if this is the "Go way", but it avoids the idea of inheritance and solves the problem. In the database logic, I use tons of reflection to grab the data from the database and populate an object with it. It leads to some unfortunate type assertions, but I guess that can't really be helped when trying to keep things abstract.
Upvotes: 4