marcusolsson / goddd

Exploring DDD in Go
MIT License
2.39k stars 275 forks source link

Returning interfaces? #28

Open longfellowone opened 5 years ago

longfellowone commented 5 years ago

Hi, Very new to GO/Programming here, very interested in implementing DDD. Having a look over your code and just have a question.

Why did you choose to return/export interfaces rather than structs/concrete types? Was it to keep from having to retype the same interface in each service?

Haven't quite figured everything out yet, maybe there is something else I'm missing?

For example the CargoRepository interface is declared in in cargo.go rather then where it is consumed?

type CargoRepository interface {
    Store(cargo *Cargo) error
    Find(id TrackingID) (*Cargo, error)
    FindAll() []*Cargo
}

Why not do something like this in your service.go?

type cargoRepository interface {
    Store(cargo *shipping.Cargo) error
    Find(id shipping.TrackingID) (*shipping.Cargo, error)
    FindAll() []*shipping.Cargo
}

type Service struct {
    cargos         cargoRepository
}

func (s *Service) Track() {
//...
}

func NewService(cargos cargoRepository) *Service {
    return &Service{
        cargos:         cargos,
    }
}

Reference: https://github.com/golang/go/wiki/CodeReviewComments#interfaces https://mycodesmells.com/post/accept-interfaces-return-struct-in-go https://stackoverflow.com/questions/37181597/go-should-i-use-an-interface-to-allow-for-mocking

alexbredy commented 5 years ago

1) CargoRepository could be used in multiple services, and the Repo is part of the domain (not the application layer), tied to the Cargo in this case.

2) The CargoRepository needs to be an interface and be exported (capital C) from the package as it is implemented in the infrastructure layer (check inmem/inmem.go and mongo/mongo.go)

longfellowone commented 5 years ago
1. CargoRepository could be used in multiple services, and the Repo is part of the domain (not the application layer), tied to the Cargo in this case.

2. The CargoRepository needs to be an interface and be exported (capital C) from the package as it is implemented in the infrastructure layer (check inmem/inmem.go and mongo/mongo.go)

Hey Alex

Since posting this Ive had a chance to play around with the DDD style a bit. Here is a solution I came up with that I think meets your requirements.

// cargo.go - package shipping
type CargoRepository interface {
    Store(cargo *shipping.Cargo) error
    Find(id shipping.TrackingID) (*shipping.Cargo, error)
    FindAll() []*shipping.Cargo
}

// service.go - package somethingelse
type Service interface {
    Track()
}

type cargoRepository interface {
    shipping.CargoRepository
}

type service struct {
    cargos cargoRepository
}

func (s *service) Track() {
    //...
}

func NewService(cargos cargoRepository) *service {
    return &service{
        cargos: cargos,
    }
}

// server.go takes the somethingelse.Service interface
func New(svc somethingelse.Service) *Server {
    //...
}

Here is an example of where Ive used this approach

https://github.com/longfellowone/field-services/blob/master/supply/purchasing/service.go#L14

This also allows you to add additional methods that are not part of the domain like in this example

https://github.com/longfellowone/field-services/blob/master/supply/ordering/service.go#L19

juliandroid commented 5 years ago

Personally I'm not a big fan of exporting data structures when they are intended to be used as (through) interfaces only. In that case you can follow this recommendation:

https://golang.org/doc/effective_go.html#generality

Also defining interfaces where they are used (as recommended in the FAQ) is not possible if in the method's prototype also refers to another interface (arguments or return type) which you have to define too, but if you do that in the same place it won't be compatible (won't implement the interface).