-
Notifications
You must be signed in to change notification settings - Fork 634
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
Make message optional in topic producer event stream #2708
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @bossqone, thanks for your efforts in improving the API.
As said in Gitter, this is a breaking API. Instead, we need to provide an alternative API.
This has been discussed a long time ago on this other issue #844
Moreover, @ignasi35 is doing some other work on topic producer that may impact it as well (see #2700).
It will be nice to coordinate on that to avoid issues.
...fka/server/src/main/scala/com/lightbend/lagom/internal/broker/kafka/TopicProducerActor.scala
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As much as I like the idea of fixing this issue in the TopicProducer
API I'm still divided on what's the best implementation. On one hand we have the PartialFunction
@renatocaval suggested in #844, then we have this which relies on Tuple2[Option[T], Offset]
and I'm thinking of a third option:
- instead of
Tuple2[Option[T], Offset]
orjapi.Pair<Optional<T>, Offset>
introduce an ADT supporting two operationsEmitAndCommit<T>(t,offset)
andJustcommit(offset)
and then provide new factory methods inTopicProducer
for that ADT.
This approach is more powerful than the two above in that the user can decide 3 different things:
- I want to emit
t
(and we force that emitting is paired with committing) - I don't want to emit anything but I want to commit this offset
- I don't want to emit and I'm fine not committing anything and I may commit some offset later.
Note how option 3.
becomes possible (as opposed to the Option
and the PartialFunction
) and allows for fewer commits into the database.
@@ -89,7 +90,7 @@ | |||
public static <Message, Event extends AggregateEvent<Event>> | |||
Topic<Message> taggedStreamWithOffset( | |||
AggregateEventShards<Event> shards, | |||
BiFunction<AggregateEventTag<Event>, Offset, Source<Pair<Message, Offset>, ?>> | |||
BiFunction<AggregateEventTag<Event>, Offset, Source<Pair<Optional<Message>, Offset>, ?>> | |||
eventStream) { | |||
return new TaggedOffsetTopicProducer<>(shards.allTags(), eventStream); | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes in TopicProducer
are the main problem in this PR as these three edits break the API. Instead, we could consider adding methods as alternatives to the existing ones. I would also mark the new methods as @ApiMayChange
and keep the original methods as non-deprecated.
Finally, I'd add a comment on the new methods to this PR or #844 so users finding the API could locate and comment on the discussion.
Hi, sorry for late response. Based on your comments it looks like more work then I naively expected. I'm fairly new to lagom/akka (or even scala) world and some stuff (e.g. binary compatibility) is unknown for me. Anyway, if this feature is something you might/want to deliver soon please take over and work on it. |
Hi @bossqone, I don't think anyone on the team is working on this, so feel free to move on at your own pace. :-) |
Hi @bossqone, a solution based on an ADT will need a public set of classes implementing that ADT in I think we will need to step back from a Then, once we have the ADT, we'll need new factory methods on |
Hi @bossqone , as we discussed elsewhere (gitter DMs) you are right in pointing out that I keep forgetting about the particularities of how broker/kafka projects are depending on each other in the lagom build. I'm affraid this takes us to the following situation:
|
Hi @ignasi35 , can you please help me understand what is |
Hmmm, that was unexpected. What is the purpose of
I'd rather not support
|
(pressed About the main question on how
So, |
In your first comment you mentioned:
But you're right, this can be achieved with |
Hi everyone, I came across this PR while looking into a solution for a use-case we have. We need to be able to publish zero or more domain events to Kafka while committing a single entity offset. I solved this problem for the time being by making a version of
Since we didn't need it, I didn't concern myself with Java support, and my solution doesn't support the "skip" case that was discussed above. My use case could be supported with the ADT approach as well, of course. I don't know if this PR is the right place for this discussion but I would love to get your thoughts on the requirement and possibly see it incorporated into this change if possible. Thanks! |
At least one pull request committer is not linked to a user. See https://help.github.com/en/articles/why-are-my-commits-linked-to-the-wrong-user#commits-are-not-linked-to-any-user |
7e8dc2c
to
e78966d
Compare
Hi @bossqone, I see you're back working on this. It's great to see you back. Timing is not great as some of us may be going on vacation in a week or two so I apologise in advance if we're not very responsive. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @bossqone, this looks really good!
I was a bit surprised to see the support for multi-message sneak into this PR. I'd rather keep things a bit simpler. I'm not familiar with the MultiMessage imlementation/semantics in Alpakka-Kafka so I'm not sure how the delivery guarantees would vary. The other concern around multi-message is that I got the impression the user is left responsible to choose what Offset
they pass into the EmitMultipleAndCommit
command. This sound more flexible but it'll require a more careful documentation.
Talking about documentation, I noticed your TODO
's for api docs, but did you plan on adding other documentation on this PR already? It'd be good to have some small guide for user to migrate and ref docs on the various commands (and the details around EmitMultipleAndCommit
I mentioned above).
Keep it up! :-)
.../src/main/scala/com/lightbend/lagom/internal/broker/kafka/InternalTopicProducerCommand.scala
Outdated
Show resolved
Hide resolved
return TaggedOffsetTopicProducer.fromEventAndOffsetPairStream(shards.allTags(), eventStream); | ||
} | ||
|
||
// TODO(bossqone): add docs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe in these docs
(or maybe somewhere else) there should be some guidance for users to migrate.
object TaggedOffsetTopicProducer { | ||
|
||
def fromTopicProducerCommandStream[Message, Event <: AggregateEvent[Event]]( | ||
tags: PSequence[AggregateEventTag[Event]], | ||
readSideStream: BiFunction[AggregateEventTag[Event], Offset, JSource[TopicProducerCommand[Message], _]] | ||
): TaggedOffsetTopicProducer[Message, Event] = new TaggedOffsetTopicProducer[Message, Event](tags, readSideStream) | ||
|
||
def fromEventAndOffsetPairStream[Message, Event <: AggregateEvent[Event]]( | ||
tags: PSequence[AggregateEventTag[Event]], | ||
readSideStream: BiFunction[AggregateEventTag[Event], Offset, JSource[Pair[Message, Offset], _]] | ||
): TaggedOffsetTopicProducer[Message, Event] = | ||
new TaggedOffsetTopicProducer[Message, Event]( | ||
tags, | ||
(tag, offset) => | ||
readSideStream.apply(tag, offset).map(pair => new TopicProducerCommand.EmitAndCommit(pair.first, pair.second)) | ||
) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These extra allocations are a good motivator for users to migrate from the old impl to the new one. Worth mentioning on the migration guide.
...ce/javadsl/broker/src/main/java/com/lightbend/lagom/javadsl/broker/TopicProducerCommand.java
Outdated
Show resolved
Hide resolved
...r/src/test/scala/com/lightbend/lagom/internal/javadsl/broker/kafka/JavadslKafkaApiSpec.scala
Show resolved
Hide resolved
...a/server/src/test/scala/com/lightbend/lagom/scaladsl/kafka/broker/ScaladslKafkaApiSpec.scala
Show resolved
Hide resolved
_.map(offset => | ||
ProjectionSpi | ||
.completedProcessing(workerCoordinates.projectionName, workerCoordinates.tagName, offset) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! The instance returned by completedProcessing
is not guaranteed to be the same as the one passed in. Your code is correct.
object TopicProducerCommand { | ||
case class EmitMultipleAndCommit[Message](messages: immutable.Seq[Message], offset: Offset) | ||
extends TopicProducerCommand[Message] | ||
case class EmitAndCommit[Message](message: Message, offset: Offset) extends TopicProducerCommand[Message] | ||
case class Commit[Message](offset: Offset) extends TopicProducerCommand[Message] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using case classes will difficult future evolution of the API.
This should be marked as @ApiMayChange
or refactorred as non-case class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Especially considering the upcoming need for metadata (#2835 ).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to normal class. InternalTopicProducerCommand
should be changed as well, or it can stay as case class?
...fka/server/src/main/scala/com/lightbend/lagom/internal/broker/kafka/TopicProducerActor.scala
Outdated
Show resolved
Hide resolved
...r/src/test/scala/com/lightbend/lagom/internal/javadsl/broker/kafka/JavadslKafkaApiSpec.scala
Show resolved
Hide resolved
Hi @ignasi35, |
final class EmitAndCommit[Message](val message: Message, val offset: Offset) extends TopicProducerCommand[Message] { | ||
override def equals(that: Any): Boolean = that match { | ||
case command: EmitAndCommit[Message] => message.equals(command.message) && offset.equals(command.offset) | ||
case _ => false | ||
} | ||
|
||
override def hashCode(): Int = Objects.hash(message.asInstanceOf[AnyRef], offset) | ||
} | ||
|
||
final class Commit[Message](val offset: Offset) extends TopicProducerCommand[Message] { | ||
override def equals(that: Any): Boolean = that match { | ||
case command: Commit[Message] => offset.equals(command.offset) | ||
case _ => false | ||
} | ||
|
||
override def hashCode(): Int = Objects.hash(offset) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great.
As this is public API there should be API docs.
In future improvements, doesn't have to be in this PR, we could add a toString
and also some factory methods in object TopicProducerCommand
so building instances of EmitAndCommit
is shorter than new TopicProducerCommand.EmitAndCommit
.
This is almost ready. I think only documentation is left. |
Pull Request Checklist
Purpose
PR introduces support for optional message in TopicProducer API. This way we can advance kafka offset even without producing message.
Background Context
Current design requires to produce
(Message, Offset)
pair for message to be published. So in case we filter out messages, it won't commit offset until another message pass through, which results in unnecessary/unwanted replays when app restarts (as mentioned in https://www.lagomframework.com/documentation/current/scala/MessageBrokerApi.html#Filtering-events).