FAQ
Hi All,

I have developed a http client for an GetEventStore

On the client there are several methods. I wanted to use interfaces to
describe the API and I think I have done it in line with good practice. the
interfaces are small as shown below.

type StreamAppender interface {
  AppendToStream(string, *StreamVersion, ...*Event) (*Response, error)
}


type MetaDataUpdater interface {
  UpdateStreamMetaData(string, interface{}) (*Response, error)
}


type MetaDataReader interface {
  GetStreamMetaData(string) (*EventResponse, *Response, error)
}


type StreamReader interface {
  ReadStreamForward(string, *StreamVersion, *Take) ([]*EventResponse, *
Response, error)
  ReadStreamBackward(string, *StreamVersion, *Take) ([]*EventResponse, *
Response, error)
}


type StreamReaderAsync interface {
  ReadStreamForwardAsync(string, *StreamVersion, *Take) <-chan struct {
  *EventResponse
  *Response
  error
  }
  ReadStreamBackwardAsync(string, *StreamVersion, *Take) <-chan struct {
  *EventResponse
  *Response
  error
  }
}


type EventBuilder interface {
  ToEventData(string, string, interface{}, interface{}) *Event
}


type EventReader interface {
  GetEvent(string) (*EventResponse, *Response, error)
  GetEvents([]string) ([]*EventResponse, *Response, error)
}

Now this helps becasue I can now inject fakes of the client into my client
code which uses the eventstore client. The constructor on the client code
now looks like this...

type GetEventStore struct {
  eventBus EventBus
  eventFactory EventFactory
  appender goes.StreamAppender
  builder goes.EventBuilder
  reader goes.StreamReader
}


func NewGetEventStore(
  eventBus EventBus,
  appender goes.StreamAppender,
  builder goes.EventBuilder,
  reader goes.StreamReader) (*GetEventStore, error) {


  s := &GetEventStore{
  eventBus: eventBus,
  appender: appender,
  builder: builder,
  reader: reader,
  }


  return s, nil
}


So far so good, however the wierdness comes with the code injecting the
eventstore into this constructor... it looks like this..

client := goes.NewClient(nil, eventStoreURL)
  eventStore, err := ycq.NewGetEventStore(eventBus, client, client, client)
  if err != nil {
  logger.Fatal("Failed to create ESEventStore", "URL", eventStoreURL, "err",
err)
  }

It feels weird to be passing the same object three times to the
constructor. Does this feel weird to anyone else or is this something I
should just get over because in every other respect it feels like the right
thing to do.

Many thanks

--
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 golang-nuts+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Search Discussions

  • GoNutter at May 9, 2016 at 12:19 pm
    Hi All,

    I have developed a http client for an GetEventStore

    On the client there are several methods. I wanted to use interfaces to
    describe the API and I think I have done it in line with good practice. the
    interfaces are small as shown below.

    type StreamAppender interface {
      AppendToStream(string, *StreamVersion, ...*Event) (*Response, error)
    }


    type MetaDataUpdater interface {
      UpdateStreamMetaData(string, interface{}) (*Response, error)
    }


    type MetaDataReader interface {
      GetStreamMetaData(string) (*EventResponse, *Response, error)
    }


    type StreamReader interface {
      ReadStreamForward(string, *StreamVersion, *Take) ([]*EventResponse, *
    Response, error)
      ReadStreamBackward(string, *StreamVersion, *Take) ([]*EventResponse, *
    Response, error)
    }


    type StreamReaderAsync interface {
      ReadStreamForwardAsync(string, *StreamVersion, *Take) <-chan struct {
      *EventResponse
      *Response
      error
      }
      ReadStreamBackwardAsync(string, *StreamVersion, *Take) <-chan struct {
      *EventResponse
      *Response
      error
      }
    }


    type EventBuilder interface {
      ToEventData(string, string, interface{}, interface{}) *Event
    }


    type EventReader interface {
      GetEvent(string) (*EventResponse, *Response, error)
      GetEvents([]string) ([]*EventResponse, *Response, error)
    }

    Now this helps becasue I can now inject fakes of the client into my client
    code which uses the eventstore client. The constructor on the client code
    now looks like this...

    type GetEventStore struct {
      eventBus EventBus
      eventFactory EventFactory
      appender goes.StreamAppender
      builder goes.EventBuilder
      reader goes.StreamReader
    }


    func NewGetEventStore(
      eventBus EventBus,
      appender goes.StreamAppender,
      builder goes.EventBuilder,
      reader goes.StreamReader) *GetEventStore {

      s := &GetEventStore{
      eventBus: eventBus,
      appender: appender,
      builder: builder,
      reader: reader,
      }

      return s
    }


    So far so good, however the wierdness comes with the code injecting the
    eventstore into this constructor... it looks like this..

    client := goes.NewClient(nil, eventStoreURL)
    eventStore := ycq.NewGetEventStore(eventBus, client, client, client)


    It feels weird to be passing the same object three times to the
    constructor. Does this feel weird to anyone else or is this something I
    should just get over because in every other respect it feels like the right
    thing to do.

    Many thanks

    --
    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 golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Egon at May 9, 2016 at 2:45 pm
    It's hard to analyse because there is no business problem you are solving.
    At the moment it's not clear what you gain from defining the interfaces
    instead of using the concrete types. I would start by getting rid of all of
    the interfaces, and then start to figure the best way to solve the business
    problem and then add interfaces as necessary.

    + Egon
    On Monday, 9 May 2016 15:19:08 UTC+3, GoNutter wrote:

    Hi All,

    I have developed a http client for an GetEventStore

    On the client there are several methods. I wanted to use interfaces to
    describe the API and I think I have done it in line with good practice. the
    interfaces are small as shown below.
    type StreamAppender interface {
    AppendToStream(string, *StreamVersion, ...*Event) (*Response, error)
    }


    type MetaDataUpdater interface {
    UpdateStreamMetaData(string, interface{}) (*Response, error)
    }


    type MetaDataReader interface {
    GetStreamMetaData(string) (*EventResponse, *Response, error)
    }


    type StreamReader interface {
    ReadStreamForward(string, *StreamVersion, *Take) ([]*EventResponse, *
    Response, error)
    ReadStreamBackward(string, *StreamVersion, *Take) ([]*EventResponse, *
    Response, error)
    }


    type StreamReaderAsync interface {
    ReadStreamForwardAsync(string, *StreamVersion, *Take) <-chan struct {
    *EventResponse
    *Response
    error
    }
    ReadStreamBackwardAsync(string, *StreamVersion, *Take) <-chan struct {
    *EventResponse
    *Response
    error
    }
    }


    type EventBuilder interface {
    ToEventData(string, string, interface{}, interface{}) *Event
    }


    type EventReader interface {
    GetEvent(string) (*EventResponse, *Response, error)
    GetEvents([]string) ([]*EventResponse, *Response, error)
    }

    Now this helps becasue I can now inject fakes of the client into my client
    code which uses the eventstore client. The constructor on the client code
    now looks like this...

    type GetEventStore struct {
    eventBus EventBus
    eventFactory EventFactory
    appender goes.StreamAppender
    builder goes.EventBuilder
    reader goes.StreamReader
    }


    func NewGetEventStore(
    eventBus EventBus,
    appender goes.StreamAppender,
    builder goes.EventBuilder,
    reader goes.StreamReader) *GetEventStore {

    s := &GetEventStore{
    eventBus: eventBus,
    appender: appender,
    builder: builder,
    reader: reader,
    }

    return s
    }


    So far so good, however the wierdness comes with the code injecting the
    eventstore into this constructor... it looks like this..

    client := goes.NewClient(nil, eventStoreURL)
    eventStore := ycq.NewGetEventStore(eventBus, client, client, client)


    It feels weird to be passing the same object three times to the
    constructor. Does this feel weird to anyone else or is this something I
    should just get over because in every other respect it feels like the right
    thing to do.

    Many thanks
    --
    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 golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • GoNutter at May 9, 2016 at 3:19 pm
    The problem I am trying to solve with the interfaces, is to make the
    consumers of the client testable by providing the ability to create fake
    implemetations that implement the interfaces.

    The reason I have them so small is to be in line with Go guidance on
    creating interfaces, to adhere to the interface segregation principle of
    SOLID and the result is that only the needed parts of the interface need to
    be implemented.
    On Monday, May 9, 2016 at 3:45:48 PM UTC+1, Egon wrote:

    It's hard to analyse because there is no business problem you are solving.
    At the moment it's not clear what you gain from defining the interfaces
    instead of using the concrete types. I would start by getting rid of all of
    the interfaces, and then start to figure the best way to solve the business
    problem and then add interfaces as necessary.

    + Egon
    On Monday, 9 May 2016 15:19:08 UTC+3, GoNutter wrote:

    Hi All,

    I have developed a http client for an GetEventStore

    On the client there are several methods. I wanted to use interfaces to
    describe the API and I think I have done it in line with good practice. the
    interfaces are small as shown below.
    type StreamAppender interface {
    AppendToStream(string, *StreamVersion, ...*Event) (*Response, error)
    }


    type MetaDataUpdater interface {
    UpdateStreamMetaData(string, interface{}) (*Response, error)
    }


    type MetaDataReader interface {
    GetStreamMetaData(string) (*EventResponse, *Response, error)
    }


    type StreamReader interface {
    ReadStreamForward(string, *StreamVersion, *Take) ([]*EventResponse, *
    Response, error)
    ReadStreamBackward(string, *StreamVersion, *Take) ([]*EventResponse, *
    Response, error)
    }


    type StreamReaderAsync interface {
    ReadStreamForwardAsync(string, *StreamVersion, *Take) <-chan struct {
    *EventResponse
    *Response
    error
    }
    ReadStreamBackwardAsync(string, *StreamVersion, *Take) <-chan struct {
    *EventResponse
    *Response
    error
    }
    }


    type EventBuilder interface {
    ToEventData(string, string, interface{}, interface{}) *Event
    }


    type EventReader interface {
    GetEvent(string) (*EventResponse, *Response, error)
    GetEvents([]string) ([]*EventResponse, *Response, error)
    }

    Now this helps becasue I can now inject fakes of the client into my
    client code which uses the eventstore client. The constructor on the client
    code now looks like this...

    type GetEventStore struct {
    eventBus EventBus
    eventFactory EventFactory
    appender goes.StreamAppender
    builder goes.EventBuilder
    reader goes.StreamReader
    }


    func NewGetEventStore(
    eventBus EventBus,
    appender goes.StreamAppender,
    builder goes.EventBuilder,
    reader goes.StreamReader) *GetEventStore {

    s := &GetEventStore{
    eventBus: eventBus,
    appender: appender,
    builder: builder,
    reader: reader,
    }

    return s
    }


    So far so good, however the wierdness comes with the code injecting the
    eventstore into this constructor... it looks like this..

    client := goes.NewClient(nil, eventStoreURL)
    eventStore := ycq.NewGetEventStore(eventBus, client, client, client)


    It feels weird to be passing the same object three times to the
    constructor. Does this feel weird to anyone else or is this something I
    should just get over because in every other respect it feels like the right
    thing to do.

    Many thanks
    --
    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 golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Egon at May 9, 2016 at 6:03 pm

    On Monday, 9 May 2016 18:19:01 UTC+3, GoNutter wrote:
    The problem I am trying to solve with the interfaces, is to make the
    consumers of the client testable by providing the ability to create fake
    implemetations that implement the interfaces.
    How do you know how the user of your library wants to test his
    implementation and where the abstraction boundaries are?

    The reason I have them so small is to be in line with Go guidance on
    creating interfaces,
    The goal is to use the minimal dependency, but not using the smallest
    interface. Most interfaces are small because the dependency is small.

    Every interface, struct, func, type will increase your API surface. This
    means it becomes harder to comprehend and use... obviously putting
    everything in a single type isn't great either.

    Interfaces are an abstraction boundary and every abstraction boundary makes
    code less direct. This means you can deal with variability, but
    (potentially) at the cost of making code harder to learn.

    to adhere to the interface segregation principle of SOLID and the result
    is that only the needed parts of the interface need to be implemented.
    Interface segregation is about separating your dependency... not about
    using interfaces. The one who depends, knows best which boundary to
    create... not the dependency itself.

    *PS: I'm not saying that you shouldn't use interfaces... What I'm saying
    you don't have a good business examples and hence you are doing a lot of
    guessing where to put the interfaces. At the moment, I would guess they are
    in the wrong place, but that's a guess. I.e. Implement several business
    examples, only then start adding interfaces -- it will ensure that the
    interface end up in the right place.*
    On Monday, May 9, 2016 at 3:45:48 PM UTC+1, Egon wrote:

    It's hard to analyse because there is no business problem you are
    solving. At the moment it's not clear what you gain from defining the
    interfaces instead of using the concrete types. I would start by getting
    rid of all of the interfaces, and then start to figure the best way to
    solve the business problem and then add interfaces as necessary.

    + Egon
    On Monday, 9 May 2016 15:19:08 UTC+3, GoNutter wrote:

    Hi All,

    I have developed a http client for an GetEventStore

    On the client there are several methods. I wanted to use interfaces to
    describe the API and I think I have done it in line with good practice. the
    interfaces are small as shown below.
    type StreamAppender interface {
    AppendToStream(string, *StreamVersion, ...*Event) (*Response, error)
    }


    type MetaDataUpdater interface {
    UpdateStreamMetaData(string, interface{}) (*Response, error)
    }


    type MetaDataReader interface {
    GetStreamMetaData(string) (*EventResponse, *Response, error)
    }


    type StreamReader interface {
    ReadStreamForward(string, *StreamVersion, *Take) ([]*EventResponse, *
    Response, error)
    ReadStreamBackward(string, *StreamVersion, *Take) ([]*EventResponse, *
    Response, error)
    }


    type StreamReaderAsync interface {
    ReadStreamForwardAsync(string, *StreamVersion, *Take) <-chan struct {
    *EventResponse
    *Response
    error
    }
    ReadStreamBackwardAsync(string, *StreamVersion, *Take) <-chan struct {
    *EventResponse
    *Response
    error
    }
    }


    type EventBuilder interface {
    ToEventData(string, string, interface{}, interface{}) *Event
    }


    type EventReader interface {
    GetEvent(string) (*EventResponse, *Response, error)
    GetEvents([]string) ([]*EventResponse, *Response, error)
    }

    Now this helps becasue I can now inject fakes of the client into my
    client code which uses the eventstore client. The constructor on the client
    code now looks like this...

    type GetEventStore struct {
    eventBus EventBus
    eventFactory EventFactory
    appender goes.StreamAppender
    builder goes.EventBuilder
    reader goes.StreamReader
    }


    func NewGetEventStore(
    eventBus EventBus,
    appender goes.StreamAppender,
    builder goes.EventBuilder,
    reader goes.StreamReader) *GetEventStore {

    s := &GetEventStore{
    eventBus: eventBus,
    appender: appender,
    builder: builder,
    reader: reader,
    }

    return s
    }


    So far so good, however the wierdness comes with the code injecting the
    eventstore into this constructor... it looks like this..

    client := goes.NewClient(nil, eventStoreURL)
    eventStore := ycq.NewGetEventStore(eventBus, client, client, client)


    It feels weird to be passing the same object three times to the
    constructor. Does this feel weird to anyone else or is this something I
    should just get over because in every other respect it feels like the right
    thing to do.

    Many thanks
    --
    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 golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • GoNutter at May 10, 2016 at 11:34 am
    Thanks Egon,

    I think that the mistake that I was making was not realising that I can
    define the interface in the client code. As you observed the iterfaces
    should belong to the client and then the client can define interfaces as
    required. This article was helpful in realising this.

    https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742#.ayj9w8tf7

    So in priniciple this all works fine, however it is turning out to be a bit
    more complex. The following interface if I define it in the client code
    does not work.

    type GetEventStoreRepositoryClient interface {
    ReadStreamForwardAsync(string, *goes.StreamVersion, *goes.Take) <-chan
    struct {
    *goes.EventResponse
    *goes.Response
    error
    }

    AppendToStream(string, *goes.StreamVersion, ...*goes.Event)
    (*goes.Response, error)
    }


    It gives the following error

    ./repository_test.go:41: cannot use store (type *goes.Client) as type
    GetEventStoreRepositoryClient in argument to NewCommonDomainRepository:

    *goes.Client does not implement GetEventStoreRepositoryClient (wrong type
    for ReadStreamForwardAsync method)

    have ReadStreamForwardAsync(string, *goes.StreamVersion, *goes.Take) <-chan
    struct { *goes.EventResponse; *goes.Response; error }

    want ReadStreamForwardAsync(string, *goes.StreamVersion, *goes.Take) <-chan
    struct { *goes.EventResponse; *goes.Response; error }

    It is something to do with the channel being returned. If I remove that
    method and just leave the append method it works fine. Also if I define the
    interface in the library rather than in the consuming code it also works.

    I think I will start another post to pursue the answer to this one as it is
    a different topic.

    Thanks for your help.


    On Monday, May 9, 2016 at 7:03:47 PM UTC+1, Egon wrote:
    On Monday, 9 May 2016 18:19:01 UTC+3, GoNutter wrote:

    The problem I am trying to solve with the interfaces, is to make the
    consumers of the client testable by providing the ability to create fake
    implemetations that implement the interfaces.
    How do you know how the user of your library wants to test his
    implementation and where the abstraction boundaries are?

    The reason I have them so small is to be in line with Go guidance on
    creating interfaces,
    The goal is to use the minimal dependency, but not using the smallest
    interface. Most interfaces are small because the dependency is small.

    Every interface, struct, func, type will increase your API surface. This
    means it becomes harder to comprehend and use... obviously putting
    everything in a single type isn't great either.

    Interfaces are an abstraction boundary and every abstraction boundary
    makes code less direct. This means you can deal with variability, but
    (potentially) at the cost of making code harder to learn.

    to adhere to the interface segregation principle of SOLID and the result
    is that only the needed parts of the interface need to be implemented.
    Interface segregation is about separating your dependency... not about
    using interfaces. The one who depends, knows best which boundary to
    create... not the dependency itself.

    *PS: I'm not saying that you shouldn't use interfaces... What I'm saying
    you don't have a good business examples and hence you are doing a lot of
    guessing where to put the interfaces. At the moment, I would guess they are
    in the wrong place, but that's a guess. I.e. Implement several business
    examples, only then start adding interfaces -- it will ensure that the
    interface end up in the right place.*
    On Monday, May 9, 2016 at 3:45:48 PM UTC+1, Egon wrote:

    It's hard to analyse because there is no business problem you are
    solving. At the moment it's not clear what you gain from defining the
    interfaces instead of using the concrete types. I would start by getting
    rid of all of the interfaces, and then start to figure the best way to
    solve the business problem and then add interfaces as necessary.

    + Egon
    On Monday, 9 May 2016 15:19:08 UTC+3, GoNutter wrote:

    Hi All,

    I have developed a http client for an GetEventStore

    On the client there are several methods. I wanted to use interfaces to
    describe the API and I think I have done it in line with good practice. the
    interfaces are small as shown below.
    type StreamAppender interface {
    AppendToStream(string, *StreamVersion, ...*Event) (*Response, error)
    }


    type MetaDataUpdater interface {
    UpdateStreamMetaData(string, interface{}) (*Response, error)
    }


    type MetaDataReader interface {
    GetStreamMetaData(string) (*EventResponse, *Response, error)
    }


    type StreamReader interface {
    ReadStreamForward(string, *StreamVersion, *Take) ([]*EventResponse, *
    Response, error)
    ReadStreamBackward(string, *StreamVersion, *Take) ([]*EventResponse, *
    Response, error)
    }


    type StreamReaderAsync interface {
    ReadStreamForwardAsync(string, *StreamVersion, *Take) <-chan struct {
    *EventResponse
    *Response
    error
    }
    ReadStreamBackwardAsync(string, *StreamVersion, *Take) <-chan struct {
    *EventResponse
    *Response
    error
    }
    }


    type EventBuilder interface {
    ToEventData(string, string, interface{}, interface{}) *Event
    }


    type EventReader interface {
    GetEvent(string) (*EventResponse, *Response, error)
    GetEvents([]string) ([]*EventResponse, *Response, error)
    }

    Now this helps becasue I can now inject fakes of the client into my
    client code which uses the eventstore client. The constructor on the client
    code now looks like this...

    type GetEventStore struct {
    eventBus EventBus
    eventFactory EventFactory
    appender goes.StreamAppender
    builder goes.EventBuilder
    reader goes.StreamReader
    }


    func NewGetEventStore(
    eventBus EventBus,
    appender goes.StreamAppender,
    builder goes.EventBuilder,
    reader goes.StreamReader) *GetEventStore {

    s := &GetEventStore{
    eventBus: eventBus,
    appender: appender,
    builder: builder,
    reader: reader,
    }

    return s
    }


    So far so good, however the wierdness comes with the code injecting the
    eventstore into this constructor... it looks like this..

    client := goes.NewClient(nil, eventStoreURL)
    eventStore := ycq.NewGetEventStore(eventBus, client, client, client)


    It feels weird to be passing the same object three times to the
    constructor. Does this feel weird to anyone else or is this something I
    should just get over because in every other respect it feels like the right
    thing to do.

    Many thanks
    --
    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 golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Egon at May 10, 2016 at 11:47 am

    On Tuesday, 10 May 2016 14:34:11 UTC+3, GoNutter wrote:
    Thanks Egon,

    I think that the mistake that I was making was not realising that I can
    define the interface in the client code. As you observed the iterfaces
    should belong to the client and then the client can define interfaces as
    required. This article was helpful in realising this.


    https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742#.ayj9w8tf7

    So in priniciple this all works fine, however it is turning out to be a
    bit more complex. The following interface if I define it in the client code
    does not work.

    type GetEventStoreRepositoryClient interface {
    ReadStreamForwardAsync(string, *goes.StreamVersion, *goes.Take) <-chan
    struct {
    *goes.EventResponse
    *goes.Response
    error
    }

    AppendToStream(string, *goes.StreamVersion, ...*goes.Event)
    (*goes.Response, error)
    }
    package eventstore

    type Client struct { ... }
    func (client *Client) Stream(name string) *Stream { ... }

    type Stream struct { ... }
    func (stream *Stream) Read() (Reader, error) { ... }
    func (stream *Stream) ReadBackwards() (Reader, error) { ... }
    func (stream *Stream) Append(event Event, expect Version) error { ... }
    func (stream *Stream) UpdateMeta(...) error { ... }

    type Reader struct { ... }
    func (reader *Reader) WaitNext() bool { ... }
    func (reader *Reader) Next() bool { ... }
    func (reader *Reader) Close() {}
    func (reader *Reader) Err() {}

    *// note: lots of guessing for the API, but should give you a rough idea*

    It gives the following error

    ./repository_test.go:41: cannot use store (type *goes.Client) as type
    GetEventStoreRepositoryClient in argument to NewCommonDomainRepository:

    *goes.Client does not implement GetEventStoreRepositoryClient (wrong type
    for ReadStreamForwardAsync method)

    have ReadStreamForwardAsync(string, *goes.StreamVersion, *goes.Take)
    <-chan struct { *goes.EventResponse; *goes.Response; error }

    want ReadStreamForwardAsync(string, *goes.StreamVersion, *goes.Take)
    <-chan struct { *goes.EventResponse; *goes.Response; error }

    It is something to do with the channel being returned. If I remove that
    method and just leave the append method it works fine. Also if I define the
    interface in the library rather than in the consuming code it also works.
    I think I will start another post to pursue the answer to this one as it
    is a different topic.
    PS: please include a link to the existing code otherwise it's difficult to
    put the whole picture together. This also means people might notice other
    mistakes that you may not have thought about.

    Also, remove the channels from the library side :). e.g. see how sql.Rows
    is implemented.

    Thanks for your help.


    On Monday, May 9, 2016 at 7:03:47 PM UTC+1, Egon wrote:
    On Monday, 9 May 2016 18:19:01 UTC+3, GoNutter wrote:

    The problem I am trying to solve with the interfaces, is to make the
    consumers of the client testable by providing the ability to create fake
    implemetations that implement the interfaces.
    How do you know how the user of your library wants to test his
    implementation and where the abstraction boundaries are?

    The reason I have them so small is to be in line with Go guidance on
    creating interfaces,
    The goal is to use the minimal dependency, but not using the smallest
    interface. Most interfaces are small because the dependency is small.

    Every interface, struct, func, type will increase your API surface. This
    means it becomes harder to comprehend and use... obviously putting
    everything in a single type isn't great either.

    Interfaces are an abstraction boundary and every abstraction boundary
    makes code less direct. This means you can deal with variability, but
    (potentially) at the cost of making code harder to learn.

    to adhere to the interface segregation principle of SOLID and the result
    is that only the needed parts of the interface need to be implemented.
    Interface segregation is about separating your dependency... not about
    using interfaces. The one who depends, knows best which boundary to
    create... not the dependency itself.

    *PS: I'm not saying that you shouldn't use interfaces... What I'm saying
    you don't have a good business examples and hence you are doing a lot of
    guessing where to put the interfaces. At the moment, I would guess they are
    in the wrong place, but that's a guess. I.e. Implement several business
    examples, only then start adding interfaces -- it will ensure that the
    interface end up in the right place.*
    On Monday, May 9, 2016 at 3:45:48 PM UTC+1, Egon wrote:

    It's hard to analyse because there is no business problem you are
    solving. At the moment it's not clear what you gain from defining the
    interfaces instead of using the concrete types. I would start by getting
    rid of all of the interfaces, and then start to figure the best way to
    solve the business problem and then add interfaces as necessary.

    + Egon
    On Monday, 9 May 2016 15:19:08 UTC+3, GoNutter wrote:

    Hi All,

    I have developed a http client for an GetEventStore

    On the client there are several methods. I wanted to use interfaces to
    describe the API and I think I have done it in line with good practice. the
    interfaces are small as shown below.
    type StreamAppender interface {
    AppendToStream(string, *StreamVersion, ...*Event) (*Response, error)
    }


    type MetaDataUpdater interface {
    UpdateStreamMetaData(string, interface{}) (*Response, error)
    }


    type MetaDataReader interface {
    GetStreamMetaData(string) (*EventResponse, *Response, error)
    }


    type StreamReader interface {
    ReadStreamForward(string, *StreamVersion, *Take) ([]*EventResponse, *
    Response, error)
    ReadStreamBackward(string, *StreamVersion, *Take) ([]*EventResponse,
    *Response, error)
    }


    type StreamReaderAsync interface {
    ReadStreamForwardAsync(string, *StreamVersion, *Take) <-chan struct {
    *EventResponse
    *Response
    error
    }
    ReadStreamBackwardAsync(string, *StreamVersion, *Take) <-chan struct
    {
    *EventResponse
    *Response
    error
    }
    }


    type EventBuilder interface {
    ToEventData(string, string, interface{}, interface{}) *Event
    }


    type EventReader interface {
    GetEvent(string) (*EventResponse, *Response, error)
    GetEvents([]string) ([]*EventResponse, *Response, error)
    }

    Now this helps becasue I can now inject fakes of the client into my
    client code which uses the eventstore client. The constructor on the client
    code now looks like this...

    type GetEventStore struct {
    eventBus EventBus
    eventFactory EventFactory
    appender goes.StreamAppender
    builder goes.EventBuilder
    reader goes.StreamReader
    }


    func NewGetEventStore(
    eventBus EventBus,
    appender goes.StreamAppender,
    builder goes.EventBuilder,
    reader goes.StreamReader) *GetEventStore {

    s := &GetEventStore{
    eventBus: eventBus,
    appender: appender,
    builder: builder,
    reader: reader,
    }

    return s
    }


    So far so good, however the wierdness comes with the code injecting
    the eventstore into this constructor... it looks like this..

    client := goes.NewClient(nil, eventStoreURL)
    eventStore := ycq.NewGetEventStore(eventBus, client, client, client)


    It feels weird to be passing the same object three times to the
    constructor. Does this feel weird to anyone else or is this something I
    should just get over because in every other respect it feels like the right
    thing to do.

    Many thanks
    --
    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 golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • GoNutter at May 10, 2016 at 12:14 pm
    Thanks Egon,

    Here is the link to the library on github.
    https://github.com/jetbasrawi/goes

    So the names of the methods have been chosen as they follow the names of
    the official clients for GetEventStore and so they follow the conventions
    surrounding the product rather than Go naming conventions.

    I will have a look at the library that you have suggested.
    On Monday, May 9, 2016 at 1:19:08 PM UTC+1, GoNutter wrote:

    Hi All,

    I have developed a http client for an GetEventStore

    On the client there are several methods. I wanted to use interfaces to
    describe the API and I think I have done it in line with good practice. the
    interfaces are small as shown below.

    type StreamAppender interface {
    AppendToStream(string, *StreamVersion, ...*Event) (*Response, error)
    }


    type MetaDataUpdater interface {
    UpdateStreamMetaData(string, interface{}) (*Response, error)
    }


    type MetaDataReader interface {
    GetStreamMetaData(string) (*EventResponse, *Response, error)
    }


    type StreamReader interface {
    ReadStreamForward(string, *StreamVersion, *Take) ([]*EventResponse, *
    Response, error)
    ReadStreamBackward(string, *StreamVersion, *Take) ([]*EventResponse, *
    Response, error)
    }


    type StreamReaderAsync interface {
    ReadStreamForwardAsync(string, *StreamVersion, *Take) <-chan struct {
    *EventResponse
    *Response
    error
    }
    ReadStreamBackwardAsync(string, *StreamVersion, *Take) <-chan struct {
    *EventResponse
    *Response
    error
    }
    }


    type EventBuilder interface {
    ToEventData(string, string, interface{}, interface{}) *Event
    }


    type EventReader interface {
    GetEvent(string) (*EventResponse, *Response, error)
    GetEvents([]string) ([]*EventResponse, *Response, error)
    }

    Now this helps becasue I can now inject fakes of the client into my client
    code which uses the eventstore client. The constructor on the client code
    now looks like this...

    type GetEventStore struct {
    eventBus EventBus
    eventFactory EventFactory
    appender goes.StreamAppender
    builder goes.EventBuilder
    reader goes.StreamReader
    }


    func NewGetEventStore(
    eventBus EventBus,
    appender goes.StreamAppender,
    builder goes.EventBuilder,
    reader goes.StreamReader) *GetEventStore {

    s := &GetEventStore{
    eventBus: eventBus,
    appender: appender,
    builder: builder,
    reader: reader,
    }

    return s
    }


    So far so good, however the wierdness comes with the code injecting the
    eventstore into this constructor... it looks like this..

    client := goes.NewClient(nil, eventStoreURL)
    eventStore := ycq.NewGetEventStore(eventBus, client, client, client)


    It feels weird to be passing the same object three times to the
    constructor. Does this feel weird to anyone else or is this something I
    should just get over because in every other respect it feels like the right
    thing to do.

    Many thanks
    --
    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 golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Egon at May 10, 2016 at 12:48 pm

    On Tuesday, 10 May 2016 15:14:35 UTC+3, GoNutter wrote:
    Thanks Egon,

    Here is the link to the library on github.
    https://github.com/jetbasrawi/goes

    So the names of the methods have been chosen as they follow the names of
    the official clients for GetEventStore and so they follow the conventions
    surrounding the product rather than Go naming conventions.
    I would suspect that, if one of the maintainers wrote Go and did an
    official client, they would use Go conventions. The library should work
    nicely with the language, not necessarily with other implementations. Every
    language imposes limits what you can write and what you cannot write, using
    the same limits as in another language doesn't make sense. The usual ES API
    look like that because, they were initially written in C# and Java, not
    necessarily because that's how they wanted to write it.

    This also means that you can simplify your code, e.g.

    e.g. https://github.com/jetbasrawi/goes/blob/master/stream.go#L23: here you
    could use type

    type StreamVersion int

    const (
    // The write should never conflict with anything and should always succeed
    Ignore = StreamVersion(-2)
    // The stream should not exist at the time of writing. This write will
    create it.
    Create = StreamVersion(-1)
    // The stream should exist but it should be empty
    Empty = StreamVersion(0)
    )

    And then you can get rid of the pointers to *StreamVersion*.

    Similarly the Take type shouldn't exist make multiple funcs instead.

    tl;dr; Make sure that the library is nice to use from Go... otherwise
    accidental complexity will start to collect to the users of the library.

    I will have a look at the library that you have suggested.
    On Monday, May 9, 2016 at 1:19:08 PM UTC+1, GoNutter wrote:

    Hi All,

    I have developed a http client for an GetEventStore

    On the client there are several methods. I wanted to use interfaces to
    describe the API and I think I have done it in line with good practice. the
    interfaces are small as shown below.

    type StreamAppender interface {
    AppendToStream(string, *StreamVersion, ...*Event) (*Response, error)
    }


    type MetaDataUpdater interface {
    UpdateStreamMetaData(string, interface{}) (*Response, error)
    }


    type MetaDataReader interface {
    GetStreamMetaData(string) (*EventResponse, *Response, error)
    }


    type StreamReader interface {
    ReadStreamForward(string, *StreamVersion, *Take) ([]*EventResponse, *
    Response, error)
    ReadStreamBackward(string, *StreamVersion, *Take) ([]*EventResponse, *
    Response, error)
    }


    type StreamReaderAsync interface {
    ReadStreamForwardAsync(string, *StreamVersion, *Take) <-chan struct {
    *EventResponse
    *Response
    error
    }
    ReadStreamBackwardAsync(string, *StreamVersion, *Take) <-chan struct {
    *EventResponse
    *Response
    error
    }
    }


    type EventBuilder interface {
    ToEventData(string, string, interface{}, interface{}) *Event
    }


    type EventReader interface {
    GetEvent(string) (*EventResponse, *Response, error)
    GetEvents([]string) ([]*EventResponse, *Response, error)
    }

    Now this helps becasue I can now inject fakes of the client into my
    client code which uses the eventstore client. The constructor on the client
    code now looks like this...

    type GetEventStore struct {
    eventBus EventBus
    eventFactory EventFactory
    appender goes.StreamAppender
    builder goes.EventBuilder
    reader goes.StreamReader
    }


    func NewGetEventStore(
    eventBus EventBus,
    appender goes.StreamAppender,
    builder goes.EventBuilder,
    reader goes.StreamReader) *GetEventStore {

    s := &GetEventStore{
    eventBus: eventBus,
    appender: appender,
    builder: builder,
    reader: reader,
    }

    return s
    }


    So far so good, however the wierdness comes with the code injecting the
    eventstore into this constructor... it looks like this..

    client := goes.NewClient(nil, eventStoreURL)
    eventStore := ycq.NewGetEventStore(eventBus, client, client, client)


    It feels weird to be passing the same object three times to the
    constructor. Does this feel weird to anyone else or is this something I
    should just get over because in every other respect it feels like the right
    thing to do.

    Many thanks
    --
    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 golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Jan Mercl at May 9, 2016 at 12:40 pm

    On Mon, May 9, 2016 at 2:05 PM GoNutter wrote:

    eventStore, err := ycq.NewGetEventStore(eventBus, client, client, client)
    It feels weird to be passing the same object three times to the
    constructor. Does this feel weird to anyone else or is this something I
    should just get over because in every other respect it feels like the right
    thing to do.

    If it's to be used more than once/twice:

             func NewFooEventStore(eb EventBus, cl Client) (EventStore, error) {
                     return NewGetEventStore(eb, cl, cl, cl)
             }

    Replace Foo with whatever word describes best what the specialized
    constructor does.

    PS: Some of the identifiers in the OP seems are unnecessary long and sound
    like javaisms. Also, ReadStreamBackward sounds strange to me b/c I call
    something a stream only when it's non-seekable.

    --

    -j

    --
    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 golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • GoNutter at May 9, 2016 at 12:55 pm
    Hi Thanks,

    No in eventsourcing it is valid to read a stream forward or backward. It is
    a persistent store of domain events rather than a live stream of data. It
    just happens to be called a stream which may have connotations of some hose
    pipe of data.

    Thankyou for the advice around the use of a contructor, I guess the
    interfaces appear correct to you as you have not said otherwise.
    On Monday, May 9, 2016 at 1:40:22 PM UTC+1, Jan Mercl wrote:

    On Mon, May 9, 2016 at 2:05 PM GoNutter <anewex...@gmail.com <javascript:>>
    wrote:
    eventStore, err := ycq.NewGetEventStore(eventBus, client, client, client)
    It feels weird to be passing the same object three times to the
    constructor. Does this feel weird to anyone else or is this something I
    should just get over because in every other respect it feels like the right
    thing to do.

    If it's to be used more than once/twice:

    func NewFooEventStore(eb EventBus, cl Client) (EventStore, error) {
    return NewGetEventStore(eb, cl, cl, cl)
    }

    Replace Foo with whatever word describes best what the specialized
    constructor does.

    PS: Some of the identifiers in the OP seems are unnecessary long and sound
    like javaisms. Also, ReadStreamBackward sounds strange to me b/c I call
    something a stream only when it's non-seekable.

    --

    -j
    --
    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 golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • GoNutter at May 9, 2016 at 1:17 pm
    Out of interest, what would better naming look like to you?
    On Monday, May 9, 2016 at 1:40:22 PM UTC+1, Jan Mercl wrote:

    On Mon, May 9, 2016 at 2:05 PM GoNutter <anewex...@gmail.com <javascript:>>
    wrote:
    eventStore, err := ycq.NewGetEventStore(eventBus, client, client, client)
    It feels weird to be passing the same object three times to the
    constructor. Does this feel weird to anyone else or is this something I
    should just get over because in every other respect it feels like the right
    thing to do.

    If it's to be used more than once/twice:

    func NewFooEventStore(eb EventBus, cl Client) (EventStore, error) {
    return NewGetEventStore(eb, cl, cl, cl)
    }

    Replace Foo with whatever word describes best what the specialized
    constructor does.

    PS: Some of the identifiers in the OP seems are unnecessary long and sound
    like javaisms. Also, ReadStreamBackward sounds strange to me b/c I call
    something a stream only when it's non-seekable.

    --

    -j
    --
    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 golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
groupgolang-nuts @
categoriesgo
postedMay 9, '16 at 12:06p
activeMay 10, '16 at 12:48p
posts12
users3
websitegolang.org

3 users in discussion

GoNutter: 7 posts Egon: 4 posts Jan Mercl: 1 post

People

Translate

site design / logo © 2021 Grokbase