I'm stuck and I hoped it wouldn't come to that.
I wanted to have interfaces for various databases aka "I wanted to support multiple databases" (not debatable).
The idea was have ModelInterface (UserModelInterface, CategoryModelInterface etc) which would wrap the model with getters setters
StorageInterface which would CRUD the modelinterfaces
and finally services which would implement higher level and more convenient functions and use storage interfaces to store data.
Well up to the point where I started creating services everything went mostly smooth.
But I hoped I could keep the services database-agnostic.
However I can't.
https://github.com/dalu/forum/tree/f39df77f5003f71f08f473970b3df1fbd29a5a43as you can see in line 19 and 20
https://github.com/dalu/forum/blob/f39df77f5003f71f08f473970b3df1fbd29a5a43/server/service/user.go#L19when I use a concrete model.User (aka line 20) everything works without error.
but when I use var m storage.ModelInterface and then call a member function of the interface (SetName)
Go panics because nil pointer dereference
=== RUN TestNewUserService
--- FAIL: TestNewUserService (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x80 pc=0x5ba9f8]
goroutine 5 [running]:
testing.tRunner.func1(0xc420068b60)
/usr/lib/go/src/testing/testing.go:622 +0x29d
panic(0x5f1520, 0x721990)
/usr/lib/go/src/runtime/panic.go:489 +0x2cf
git.icod.de/dalu/forum/server/service.(*UserService).CreateUser(0xc420055f40, 0x62f170, 0x8, 0x630677, 0xd, 0x62e844, 0x6, 0x7ffdb1d66ba1, 0xc420031f68)
/home/darko/go/src/git.icod.de/dalu/forum/server/service/user.go:19 +0x28
git.icod.de/dalu/forum/server/service.TestNewUserService(0xc420068b60)
/home/darko/go/src/git.icod.de/dalu/forum/server/service/user_test.go:23 +0x1f9
testing.tRunner(0xc420068b60, 0x63af98)
/usr/lib/go/src/testing/testing.go:657 +0x96
created by testing.(*T).Run
/usr/lib/go/src/testing/testing.go:697 +0x2ca
exit status 2
FAIL git.icod.de/dalu/forum/server/service 0.005s
However I don't know what the problem was that I had previously an interface *does have* allocated memory but not in the same way the concrete model that fits the interface does so they're not compatible, which lead me to some headaches.
So when an interface has memory allocated, why can't I use it as if it has memory allocated?
As it is right now I'll have to dump all the StorageInterface and ModelInterface interfaces because it doesn't make sense to use them since I'm bound to a specific model/db at the topmost level,which is "service".
"And this is why we can't have nice things".
Removing those would also lower the memory footprint when querying for multiple results (slices, find/readall).
It's sad, I really wanted to, but I can't.
So lesson learned:
- keep models the way they are, conrete for the expected database. A mongodb model is different from a pgsql model is different from a cassandra model
- storage/repository can be concrete and it should just do CRUD
- interface only at the top level, aka service
- there is no 1 fits all in Go
routes <> handler <interface> service <concrete> storage/repository <concrete> model
effectively
MySQLUserService
MongoDBUserService
CassandraUserService
RethinkDBUserService
but
type UserService interface {
//...
}
Question, is there any sane way to solve it without throwing it all away? Hope dies last. Any trick to make it still work with interfaces on the service level without needing to create a concrete database bound instance of a model/struct?