Don't put organize by structure, this is what seems to be causing the most
of the damage,
I would suggest implementing as:
assets/*
cmd/root.go
cmd/server.go
storage/mongo/user.go
storage/id.go
user/user.go
user/service.go
forum/server.go
main.go
User package would look like:
package user
import (
"git.icod.de/dalu/forum/server/storage"
)
type User struct {
ID storage.ID `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
Email string `bson:"email" json:"email"`
Password []byte `bson:"password" json:"-"`
ActivationCode string `bson:"activation_code,omitempty"
json:"activation_code,omitempty"`
RecoveryCode string `bson:"recovery_code,omitempty"
json:"recovery_code,omitempty"`
Roles []string `bson:"roles" json:"roles"`
}
type Storage interface {
Create(m *User) error
Update(id string, m *User) error
ReadOne(id string) (*User, error)
ReadOneBy(query map[string]interface{}) (*User, error)
ReadAll(sort string, start, limit int) ([]*User, error)
ReadAllBy(query map[string]interface{}, sort string, start, limit int)
([]UserModelInterface, error)
Delete(id string) error
DeleteBy(query map[string]interface{}) error
}
type Service struct {
storage Storage
}
func NewService(storage Storage) *Service {
return &Service{storage}
}
func (s *Service) CreateUser(name, email, password string) error {
user := &User{}
user.Name = name
user.Email = email
// ...
return s.storage.Create(m)
}
func (s *Service) VerifyActivationCode(email string) {
// ...
}
It will make your whole code easier to manage. Names will become much nicer
and clearer. Lots of interfaces will disappear.
For storage.ID, it can be implemented as *interface{ Set(v string);
String() string }* or *struct { I uint64; B bson.ObjectId }* (with custom
marshalers)...
+ Egon
On Sunday, 28 May 2017 21:40:01 UTC+3, Darko Luketic wrote:
>
> 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/f39df77f5003f71f08f473970b3df1fbd29a5a43
>
> as you can see in line 19 and 20
>
> https://github.com/dalu/forum/blob/f39df77f5003f71f08f473970b3df1fbd29a5a43/server/service/user.go#L19
>
> when 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?
>
--
You received this message because you are subscribed to the Google Groups
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.