vardius / go-api-boilerplate

Go Server/API boilerplate using best practices DDD CQRS ES gRPC
https://go-api-boilerplate.local
MIT License
929 stars 135 forks source link

[question] domain pollution #106

Open SpareShade opened 2 years ago

SpareShade commented 2 years ago

Hello @vardius

Thank you for sharing this library, it is very helpful in many regards!

My question is about your choice of appending the metadata in the aggregate itself. Eg.

func (u *User) trackChange(ctx context.Context, event *domain.Event) error {
    var meta domain.EventMetadata
    if i, hasIdentity := identity.FromContext(ctx); hasIdentity {
        meta.Identity = i
    }
    if m, ok := metadata.FromContext(ctx); ok {
        meta.IPAddress = m.IPAddress
        meta.UserAgent = m.UserAgent
        meta.Referer = m.Referer
    }
    if !meta.IsEmpty() {
        event.WithMetadata(&meta)
    }

    u.changes = append(u.changes, event)
    u.version++

    return nil
}

Seeing the UserAgent or IP exposed in the aggregate feels a bit like pollution of a domain with application/infra data. I understand that the aggregate essentially proxies this data from the command handler, which usually is acceptable practice, as aggregate is not making use of that data.

I am wondering if you would not mind sharing your thoughts as to why you chose to enrich the event in the aggregate as opposed to eg. in the aggregate repository implementation.

Thanks again for sharing!

vardius commented 2 years ago

The reason why I did that is because with the message bus I am using the context is not carried over

SpareShade commented 2 years ago

Thank you for getting back on this question.

But aren't the events handled first by the repository, which then publishes them to the event bus? The repository should still have access to the request context, or is this not so.

// Save current user changes to event store and publish each event with an event bus
func (r *userRepository) Save(ctx context.Context, u user.User) error {
    if err := r.eventStore.Store(ctx, u.Changes()); err != nil {
        return apperrors.Wrap(err)
    }

    for _, event := range u.Changes() {
        if err := r.eventBus.Publish(ctx, event); err != nil {
            return apperrors.Wrap(err)
        }
    }

    return nil
}

I don't mean to nitpick. I'm just drawing a lot from your code (very nicely written too), and want to see whether there are potential pitfalls in my implementation/thinking. And of course, it's best to think together :)

vardius commented 2 years ago

yes you are right! i suppose i have missed that. i agree it would feel much better in the repository instead of having this in the domain. would you like to contribute and move it yourself? i would happily merge your pr :) and it feels like an easy change

SpareShade commented 2 years ago

Yes, I can absolutely, glad you agree :).

I am tied up atm, but should have a gap next weekend, it won't be too long in any case :)

vardius commented 2 years ago

ok cool, will w8 ;)