Skip to content

Commit a5cedf9

Browse files
feat: Change message id on PubsubMessages to be an encoded MessageMetadata and rename PublishMetadata to MessageMetadata (#482)
* feat: Change message id on PubsubMessages to be an encoded PublishMetadata PublishMetadata is both the partition and offset, and is an ID suitable for deduplication. FIXES: #471 * feat: Rename PublishMetadata to MessageMetadata * fix: Run fmt:format on samples
1 parent fe29e70 commit a5cedf9

26 files changed

+197
-94
lines changed

.readme-partials.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,11 @@ custom_content: |
110110
futures.add(future);
111111
}
112112
} finally {
113-
ArrayList<PublishMetadata> metadata = new ArrayList<>();
113+
ArrayList<MessageMetadata> metadata = new ArrayList<>();
114114
List<String> ackIds = ApiFutures.allAsList(futures).get();
115115
for (String id : ackIds) {
116116
// Decoded metadata contains partition and offset.
117-
metadata.add(PublishMetadata.decode(id));
117+
metadata.add(MessageMetadata.decode(id));
118118
}
119119
System.out.println(metadata + "\nPublished " + ackIds.size() + " messages.");
120120

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,11 +188,11 @@ try {
188188
futures.add(future);
189189
}
190190
} finally {
191-
ArrayList<PublishMetadata> metadata = new ArrayList<>();
191+
ArrayList<MessageMetadata> metadata = new ArrayList<>();
192192
List<String> ackIds = ApiFutures.allAsList(futures).get();
193193
for (String id : ackIds) {
194194
// Decoded metadata contains partition and offset.
195-
metadata.add(PublishMetadata.decode(id));
195+
metadata.add(MessageMetadata.decode(id));
196196
}
197197
System.out.println(metadata + "\nPublished " + ackIds.size() + " messages.");
198198

google-cloud-pubsublite/clirr-ignored-differences.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!-- see http://www.mojohaus.org/clirr-maven-plugin/examples/ignored-differences.html -->
33
<differences>
4+
<!-- TODO: Remove on next release -->
5+
<difference>
6+
<differenceType>8001</differenceType>
7+
<className>com/google/cloud/pubsublite/PublishMetadata</className>
8+
<method>*</method>
9+
</difference>
410
<!-- Added abstract method to AutoValue.Builder class (Always okay) -->
511
<difference>
612
<differenceType>7013</differenceType>

google-cloud-pubsublite/src/main/java/com/google/cloud/pubsublite/PublishMetadata.java renamed to google-cloud-pubsublite/src/main/java/com/google/cloud/pubsublite/MessageMetadata.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,32 +24,33 @@
2424
import com.google.cloud.pubsublite.internal.CheckedApiException;
2525

2626
/**
27-
* Information about a successful publish operation. Can be encoded in the string returned by the
28-
* Cloud Pub/Sub {@link com.google.cloud.pubsub.v1.Publisher#publish} api.
27+
* Information about a message in Pub/Sub Lite. Can be encoded in the string returned by the Cloud
28+
* Pub/Sub {@link com.google.cloud.pubsub.v1.Publisher#publish} api or the {@link
29+
* com.google.pubsub.v1.PubsubMessage#getMessageId} field on received messages.
2930
*/
3031
@AutoValue
31-
public abstract class PublishMetadata {
32+
public abstract class MessageMetadata {
3233
/** The partition a message was published to. */
3334
public abstract Partition partition();
3435

3536
/** The offset a message was assigned. */
3637
public abstract Offset offset();
3738

38-
/** Construct a PublishMetadata from a Partition and Offset. */
39-
public static PublishMetadata of(Partition partition, Offset offset) {
40-
return new AutoValue_PublishMetadata(partition, offset);
39+
/** Construct a MessageMetadata from a Partition and Offset. */
40+
public static MessageMetadata of(Partition partition, Offset offset) {
41+
return new AutoValue_MessageMetadata(partition, offset);
4142
}
4243

43-
/** Decode a PublishMetadata from the Cloud Pub/Sub ack id. */
44-
public static PublishMetadata decode(String encoded) throws ApiException {
44+
/** Decode a MessageMetadata from the Cloud Pub/Sub ack id. */
45+
public static MessageMetadata decode(String encoded) throws ApiException {
4546
String[] split = encoded.split(":");
46-
checkArgument(split.length == 2, "Invalid encoded PublishMetadata.");
47+
checkArgument(split.length == 2, "Invalid encoded MessageMetadata.");
4748
try {
4849
Partition partition = Partition.of(Long.parseLong(split[0]));
4950
Offset offset = Offset.of(Long.parseLong(split[1]));
5051
return of(partition, offset);
5152
} catch (NumberFormatException e) {
52-
throw new CheckedApiException("Invalid encoded PublishMetadata.", e, Code.INVALID_ARGUMENT)
53+
throw new CheckedApiException("Invalid encoded MessageMetadata.", e, Code.INVALID_ARGUMENT)
5354
.underlying;
5455
}
5556
}

google-cloud-pubsublite/src/main/java/com/google/cloud/pubsublite/cloudpubsub/MessageTransforms.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
import com.google.api.gax.rpc.ApiException;
2222
import com.google.api.gax.rpc.StatusCode.Code;
2323
import com.google.cloud.pubsublite.Message;
24+
import com.google.cloud.pubsublite.MessageMetadata;
2425
import com.google.cloud.pubsublite.MessageTransformer;
26+
import com.google.cloud.pubsublite.Partition;
2527
import com.google.cloud.pubsublite.SequencedMessage;
2628
import com.google.cloud.pubsublite.internal.CheckedApiException;
2729
import com.google.common.collect.ImmutableListMultimap;
@@ -72,15 +74,30 @@ private static String parseAttributes(Collection<ByteString> values) throws ApiE
7274
"Received an unparseable message with multiple values for an attribute.");
7375
ByteString attribute = values.iterator().next();
7476
checkArgument(
75-
attribute.isValidUtf8(), "Received an unparseable message with a non-utf8 attribute.");
77+
attribute.isValidUtf8(),
78+
String.format(
79+
"Received an unparseable message with a non-utf8 attribute value: %s",
80+
Base64.getEncoder().encodeToString(attribute.toByteArray())));
7681
return attribute.toStringUtf8();
7782
}
7883

84+
static MessageTransformer<SequencedMessage, PubsubMessage> addIdCpsSubscribeTransformer(
85+
Partition partition, MessageTransformer<SequencedMessage, PubsubMessage> toWrap) {
86+
return message -> {
87+
PubsubMessage out = toWrap.transform(message);
88+
checkArgument(
89+
out.getMessageId().isEmpty(),
90+
String.format("Received non-empty message id for PubsubMessage: %s", out));
91+
return out.toBuilder()
92+
.setMessageId(MessageMetadata.of(partition, message.offset()).encode())
93+
.build();
94+
};
95+
}
96+
7997
public static MessageTransformer<SequencedMessage, PubsubMessage> toCpsSubscribeTransformer() {
8098
return message -> {
8199
PubsubMessage.Builder outBuilder =
82100
toCpsPublishTransformer().transform(message.message()).toBuilder();
83-
outBuilder.setMessageId(Long.toString(message.offset().value()));
84101
outBuilder.setPublishTime(message.publishTime());
85102
return outBuilder.build();
86103
};

google-cloud-pubsublite/src/main/java/com/google/cloud/pubsublite/cloudpubsub/SubscriberSettings.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,10 @@ public abstract class SubscriberSettings {
8888
*/
8989
abstract List<Partition> partitions();
9090

91-
/** The MessageTransformer to get PubsubMessages from Pub/Sub Lite wire messages. */
91+
/**
92+
* The MessageTransformer to get PubsubMessages from Pub/Sub Lite wire messages. The messageId
93+
* field must not be set on the returned message.
94+
*/
9295
abstract Optional<MessageTransformer<SequencedMessage, PubsubMessage>> transformer();
9396

9497
/** A provider for credentials. */
@@ -155,7 +158,10 @@ public abstract static class Builder {
155158
*/
156159
public abstract Builder setPartitions(List<Partition> partition);
157160

158-
/** The MessageTransformer to get PubsubMessages from Pub/Sub Lite wire messages. */
161+
/**
162+
* The MessageTransformer to get PubsubMessages from Pub/Sub Lite wire messages. The messageId
163+
* field must not be set on the returned message.
164+
*/
159165
public abstract Builder setTransformer(
160166
MessageTransformer<SequencedMessage, PubsubMessage> transformer);
161167

@@ -243,7 +249,8 @@ Subscriber newPartitionSubscriber(Partition partition) throws CheckedApiExceptio
243249

244250
return new SinglePartitionSubscriber(
245251
receiver(),
246-
transformer().orElse(MessageTransforms.toCpsSubscribeTransformer()),
252+
MessageTransforms.addIdCpsSubscribeTransformer(
253+
partition, transformer().orElse(MessageTransforms.toCpsSubscribeTransformer())),
247254
new AckSetTrackerImpl(wireCommitter),
248255
nackHandler().orElse(new NackHandler() {}),
249256
messageConsumer -> wireSubscriberBuilder.setMessageConsumer(messageConsumer).build(),

google-cloud-pubsublite/src/main/java/com/google/cloud/pubsublite/cloudpubsub/internal/WrappingPublisher.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,22 @@
2222
import com.google.api.core.ApiFutures;
2323
import com.google.api.gax.rpc.ApiException;
2424
import com.google.cloud.pubsublite.Message;
25+
import com.google.cloud.pubsublite.MessageMetadata;
2526
import com.google.cloud.pubsublite.MessageTransformer;
26-
import com.google.cloud.pubsublite.PublishMetadata;
2727
import com.google.cloud.pubsublite.cloudpubsub.Publisher;
2828
import com.google.cloud.pubsublite.internal.CheckedApiException;
2929
import com.google.cloud.pubsublite.internal.TrivialProxyService;
3030
import com.google.common.util.concurrent.MoreExecutors;
3131
import com.google.pubsub.v1.PubsubMessage;
3232

3333
// A WrappingPublisher wraps the wire protocol client with a Cloud Pub/Sub api compliant
34-
// publisher. It encodes a PublishMetadata object in the response string.
34+
// publisher. It encodes a MessageMetadata object in the response string.
3535
public class WrappingPublisher extends TrivialProxyService implements Publisher {
36-
private final com.google.cloud.pubsublite.internal.Publisher<PublishMetadata> wirePublisher;
36+
private final com.google.cloud.pubsublite.internal.Publisher<MessageMetadata> wirePublisher;
3737
private final MessageTransformer<PubsubMessage, Message> transformer;
3838

3939
public WrappingPublisher(
40-
com.google.cloud.pubsublite.internal.Publisher<PublishMetadata> wirePublisher,
40+
com.google.cloud.pubsublite.internal.Publisher<MessageMetadata> wirePublisher,
4141
MessageTransformer<PubsubMessage, Message> transformer)
4242
throws ApiException {
4343
super(wirePublisher);
@@ -58,7 +58,7 @@ public ApiFuture<String> publish(PubsubMessage message) {
5858
}
5959
return ApiFutures.transform(
6060
wirePublisher.publish(wireMessage),
61-
PublishMetadata::encode,
61+
MessageMetadata::encode,
6262
MoreExecutors.directExecutor());
6363
}
6464
}

google-cloud-pubsublite/src/main/java/com/google/cloud/pubsublite/internal/wire/PartitionCountWatchingPublisher.java

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
import com.google.api.core.ApiFutures;
2424
import com.google.api.core.ApiService;
2525
import com.google.cloud.pubsublite.Message;
26+
import com.google.cloud.pubsublite.MessageMetadata;
2627
import com.google.cloud.pubsublite.Partition;
27-
import com.google.cloud.pubsublite.PublishMetadata;
2828
import com.google.cloud.pubsublite.internal.CheckedApiException;
2929
import com.google.cloud.pubsublite.internal.CloseableMonitor;
3030
import com.google.cloud.pubsublite.internal.ProxyService;
@@ -39,23 +39,23 @@
3939
import java.util.stream.LongStream;
4040

4141
public class PartitionCountWatchingPublisher extends ProxyService
42-
implements Publisher<PublishMetadata> {
42+
implements Publisher<MessageMetadata> {
4343
private static final GoogleLogger log = GoogleLogger.forEnclosingClass();
4444
private final PartitionPublisherFactory publisherFactory;
4545
private final RoutingPolicy.Factory policyFactory;
4646

4747
private static class PartitionsWithRouting {
48-
public final ImmutableMap<Partition, Publisher<PublishMetadata>> publishers;
48+
public final ImmutableMap<Partition, Publisher<MessageMetadata>> publishers;
4949
private final RoutingPolicy routingPolicy;
5050

5151
private PartitionsWithRouting(
52-
ImmutableMap<Partition, Publisher<PublishMetadata>> publishers,
52+
ImmutableMap<Partition, Publisher<MessageMetadata>> publishers,
5353
RoutingPolicy routingPolicy) {
5454
this.publishers = publishers;
5555
this.routingPolicy = routingPolicy;
5656
}
5757

58-
public ApiFuture<PublishMetadata> publish(Message message) throws CheckedApiException {
58+
public ApiFuture<MessageMetadata> publish(Message message) throws CheckedApiException {
5959
try {
6060
Partition routedPartition =
6161
message.key().isEmpty()
@@ -73,13 +73,13 @@ public ApiFuture<PublishMetadata> publish(Message message) throws CheckedApiExce
7373
}
7474

7575
public void cancelOutstandingPublishes() {
76-
for (Publisher<PublishMetadata> publisher : publishers.values()) {
76+
for (Publisher<MessageMetadata> publisher : publishers.values()) {
7777
publisher.cancelOutstandingPublishes();
7878
}
7979
}
8080

8181
public void flush() throws IOException {
82-
for (Publisher<PublishMetadata> publisher : publishers.values()) {
82+
for (Publisher<MessageMetadata> publisher : publishers.values()) {
8383
publisher.flush();
8484
}
8585
}
@@ -109,7 +109,7 @@ public void stop() {
109109
}
110110

111111
@Override
112-
public ApiFuture<PublishMetadata> publish(Message message) {
112+
public ApiFuture<MessageMetadata> publish(Message message) {
113113
Optional<PartitionsWithRouting> partitions;
114114
try (CloseableMonitor.Hold h = monitor.enter()) {
115115
partitions = partitionsWithRouting;
@@ -150,12 +150,12 @@ public void flush() throws IOException {
150150
partitions.get().flush();
151151
}
152152

153-
private ImmutableMap<Partition, Publisher<PublishMetadata>> getNewPartitionPublishers(
153+
private ImmutableMap<Partition, Publisher<MessageMetadata>> getNewPartitionPublishers(
154154
LongStream newPartitions) {
155-
ImmutableMap.Builder<Partition, Publisher<PublishMetadata>> mapBuilder = ImmutableMap.builder();
155+
ImmutableMap.Builder<Partition, Publisher<MessageMetadata>> mapBuilder = ImmutableMap.builder();
156156
newPartitions.forEach(
157157
i -> {
158-
Publisher<PublishMetadata> p = publisherFactory.newPublisher(Partition.of(i));
158+
Publisher<MessageMetadata> p = publisherFactory.newPublisher(Partition.of(i));
159159
p.addListener(
160160
new Listener() {
161161
@Override
@@ -167,7 +167,7 @@ public void failed(State from, Throwable failure) {
167167
mapBuilder.put(Partition.of(i), p);
168168
p.startAsync();
169169
});
170-
ImmutableMap<Partition, Publisher<PublishMetadata>> partitions = mapBuilder.build();
170+
ImmutableMap<Partition, Publisher<MessageMetadata>> partitions = mapBuilder.build();
171171
partitions.values().forEach(ApiService::awaitRunning);
172172
return partitions;
173173
}
@@ -189,12 +189,12 @@ private void handleConfig(long partitionCount) {
189189
partitionCount);
190190
return;
191191
}
192-
ImmutableMap.Builder<Partition, Publisher<PublishMetadata>> mapBuilder =
192+
ImmutableMap.Builder<Partition, Publisher<MessageMetadata>> mapBuilder =
193193
ImmutableMap.builder();
194194
current.ifPresent(p -> p.publishers.forEach(mapBuilder::put));
195195
getNewPartitionPublishers(LongStream.range(currentSize, partitionCount))
196196
.forEach(mapBuilder::put);
197-
ImmutableMap<Partition, Publisher<PublishMetadata>> newMap = mapBuilder.build();
197+
ImmutableMap<Partition, Publisher<MessageMetadata>> newMap = mapBuilder.build();
198198

199199
partitionsWithRouting =
200200
Optional.of(

google-cloud-pubsublite/src/main/java/com/google/cloud/pubsublite/internal/wire/PartitionCountWatchingPublisherSettings.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public abstract static class Builder {
5454
public abstract PartitionCountWatchingPublisherSettings build();
5555
}
5656

57-
public Publisher<PublishMetadata> instantiate() throws ApiException {
57+
public Publisher<MessageMetadata> instantiate() throws ApiException {
5858
return new PartitionCountWatchingPublisher(
5959
publisherFactory(),
6060
DefaultRoutingPolicy::new,

google-cloud-pubsublite/src/main/java/com/google/cloud/pubsublite/internal/wire/PartitionPublisherFactory.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
package com.google.cloud.pubsublite.internal.wire;
1818

1919
import com.google.api.gax.rpc.ApiException;
20+
import com.google.cloud.pubsublite.MessageMetadata;
2021
import com.google.cloud.pubsublite.Partition;
21-
import com.google.cloud.pubsublite.PublishMetadata;
2222
import com.google.cloud.pubsublite.internal.Publisher;
2323

2424
public interface PartitionPublisherFactory {
25-
Publisher<PublishMetadata> newPublisher(Partition partition) throws ApiException;
25+
Publisher<MessageMetadata> newPublisher(Partition partition) throws ApiException;
2626
}

0 commit comments

Comments
 (0)