Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add v3 version of nats jetstream protocol with integration tests and samples #1095

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

stephen-totty-hpe
Copy link
Contributor

As been discussed previously, there has been a desire to implement the CloudEventSDK nats jetstream protocol using the newer jetstream package. The newer jetstream package exposes more jetstream specific functionality that should make it more flexible to use.

Much of this code is stolen from the v2 implementation and changed where necessary. It builds upon comments made in:
#1083

Under the covers, this uses a jetstream.Consumer, which can be "normal" or "ordered". This is done using consumer options WithConsumerConfig and WithOrderedConsumerConfig.

Also, the need to know the stream names upfront is not needed. Many of the internal options are available if needed, although the only required option is the ConsumerOption with the correct config.

@stephen-totty-hpe stephen-totty-hpe requested a review from a team as a code owner September 10, 2024 21:18
Copy link
Member

@embano1 embano1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding the samples, please take a look at how we use a protocol in https://github.com/cloudevents/sdk-go/blob/main/samples/kafka_confluent/sender/main.go

Comment on lines 31 to 54
func WithConsumerOptions(opts ...ConsumerOption) ProtocolOption {
return func(p *Protocol) error {
p.consumerOptions = opts
return nil
}
}

func WithSenderOptions(opts ...SenderOption) ProtocolOption {
return func(p *Protocol) error {
p.senderOptions = opts
return nil
}
}

// WithPublishOptions configures the Sender
func WithPublishOptions(publishOpts []jetstream.PublishOpt) SenderOption {
return func(s *Sender) error {
s.PublishOpts = publishOpts
return nil
}
}

// WithConsumerConfig configures the Consumer with the given config
func WithConsumerConfig(consumerConfig *jetstream.ConsumerConfig) ConsumerOption {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still confused why we need different options/types for publisher/sender and consumer. Is there no way to hide more implementation details and just have one option type for either publisher/consumer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was able to combine the SenderOptions, CustomerOptions, and JetstreamOptions into the one and only ProtocolOptions.

I was not able to combine the NatsOptions because the NatsOptions are used early in the process to create the connection.

protocol/nats_jetstream/v3/options.go Outdated Show resolved Hide resolved
protocol/nats_jetstream/v3/options.go Outdated Show resolved Hide resolved
protocol/nats_jetstream/v3/options_test.go Outdated Show resolved Hide resolved
protocol/nats_jetstream/v3/options_test.go Outdated Show resolved Hide resolved
protocol/nats_jetstream/v3/sender.go Outdated Show resolved Hide resolved
samples/nats_jetstream/v3/message-interoperability/main.go Outdated Show resolved Hide resolved
samples/nats_jetstream/v3/message-interoperability/main.go Outdated Show resolved Hide resolved
samples/nats_jetstream/v3/message-interoperability/main.go Outdated Show resolved Hide resolved
samples/nats_jetstream/v3/receiver/main.go Outdated Show resolved Hide resolved
@stephen-totty-hpe stephen-totty-hpe force-pushed the totty/nats-v3 branch 5 times, most recently from b5e1c62 to 16961e8 Compare September 16, 2024 20:32
…samples

Signed-off-by: stephen-totty-hpe <stephen.totty@hpe.com>

// NatsOptions is a helper function to group a variadic nats.ProtocolOption into
// []nats.Option that can be used by either Sender, Consumer or Protocol
func NatsOptions(opts ...nats.Option) []nats.Option {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: IMHO this helper can either by internal if we need it or removed because it increases the API for no real benefit

type Protocol struct {
conn *nats.Conn

Consumer *consumer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: why do we need to keep those still public?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in integration test:
cleanup, s, r := testProtocol(ctx, t, conn, tt.args.consumerConfig, tt.args.opts...)
the sender and receiver needs to be accesible from the protocol in order to call
test.SendReceive(t, binding.WithPreferredEventEncoding(context.TODO(), tt.args.bindingEncoding), in, s, r, ...

I can add a GetSender() or GetConsumer() in order to get the values from the Protocol object.

}

// NewProtocol creates a new NATS protocol.
func NewProtocol(ctx context.Context, url, sendSubject string, natsOpts []nats.Option, opts ...ProtocolOption) (*Protocol, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: is natsOpts optional (can be nil/0 len) or required? If not, please make it a ProtocolOption otherwise check for its validity and return error early

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, is url and sendSubject required or can it be "" ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

url is required by both Consumer and Sender. sendSubject is required by Sender.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

natsOpts is optional, but is highly used for things like credentials for security.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with natsOpts, is that I need it before applying the options in order to get a *nats.Conn. So a sort of chicken-egg problem. I need the *nats.Conn to create a consumer, but I need a consumer to apply an option.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could set the *nats.Conn on the consumer after applying the options, but I would have to see if any other option or logic needs the *nats.Conn sooner.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you like, I can add three more options:
func WithURL(url string, natsOpts []nats.Option) ProtocolOption
func WithConnection(conn *nats.Conn) ProtocolOption
func WithSenderSubject(sendSubject string) ProtocolOption

And then I could reduce to only one constructor that takes (ctx context.Context, opts ...ProtocolOption)

Of course, there would be more errors needed in case a Sender was created without sendSubject.
Also "one and only one" of WithURL/WithConnection must be provided.

}

// NewProtocolFromConn creates a new NATS protocol with the given nats connection.
func NewProtocolFromConn(ctx context.Context, conn *nats.Conn, sendSubject string, opts ...ProtocolOption) (*Protocol, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: should we accept an interface for nats.Conn here to simplify mocking or hardcode it to this type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfourtunately, the jetstream library explictly uses a "pointer to struct"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants