Skip to content

Commit ceb877d

Browse files
fixup! EDU-1469: Re-writes idempotent publishing section to improve clarity to the reader
1 parent 9390d6d commit ceb877d

File tree

1 file changed

+96
-205
lines changed

1 file changed

+96
-205
lines changed

content/channels/index.textile

Lines changed: 96 additions & 205 deletions
Original file line numberDiff line numberDiff line change
@@ -753,124 +753,47 @@ channel.Publish(context.Background(), "action", "boom!")
753753

754754
h3(#idempotency). Idempotent publish
755755

756-
Ably supports idempotent publishing through its REST and realtime interfaces, ensuring that duplicate messages are not processed or forwarded.
756+
Idempotent publishing ensures that multiple attempts to publish the same message do not result in duplicate messages. This feature is essential when network issues may cause a client to reattempt message publication without knowing if the initial attempt succeeded.
757757

758-
Idempotent publishing automatically assigns each message a unique ID, which prevents further processing if the same message is published again, recognizing it as a duplicate. This is essential for handling retries in poor network conditions.
758+
h4. Automatic idempotency
759759

760-
In certain instances, idempotent publishing requires a client-supplied ID. This is particularly useful when it is impractical to maintain a single, continuously active client instance, such as during a publisher restart or when maintaining idempotency across an entire message processing pipeline involving an upstream system using message IDs. Specifying a custom message ID for REST and realtime publishing in these scenarios ensures idempotency.
760+
For versions 1.2 and later of the Ably library, idempotent publishing is enabled by default. This means the Ably SDK automatically assigns a unique ID to each message to prevent duplicates during retries. This functionality applies to both REST and realtime publishing.
761+
For older versions (before 1.2), this feature was not enabled by default for publishing. However, it could be activated using @idempotentRestPublishing@ in "@ClientOptions@":/api/rest-sdk#client-optionsidempotentRestPublishing.
761762

762-
<aside data-type='note'>
763-
<p>From version 1.2, idempotent publishing is enabled by default for both REST and realtime interfaces. In earlier versions, enabling idempotent REST and realtime publishing was not the default and required activation using the @idempotentRestPublishing@ option in "@ClientOptions@.":/api/rest-sdk#client-options</p>
764-
</aside>
763+
h4. Client ID idempotency
765764

766-
The following example manually specifies a message ID:
765+
There are scenarios where specifying a client-supplied ID to achieve idempotency are necessary, such as:
767766

768-
```[realtime_javascript]
769-
const realtime = new Ably.Realtime = '{{API_KEY}}';
770-
const channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}');
771-
const message = [{ data: 'payload', id: 'unique123' }];
767+
* Ensuring idempotency when a publisher instance might be restarted, and continuous activity cannot be guaranteed.
768+
* Integrating with an upstream system that uses its message IDs, ensuring idempotency across an entire message processing pipeline.
772769

773-
try {
774-
await channel.publish(message);
775-
console.log('Message published successfully');
776-
} catch (err) {
777-
console.log('Failed to publish message:', err.message);
778-
}
779-
```
780-
781-
```[rest_javascript]
782-
const rest = new Ably.Rest = '{{API_KEY}}';
783-
const channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}');
784-
const message = [{ data: 'payload', id: 'unique123' }];
770+
In these cases, you can manually specify the message ID for REST and realtime publishing.
785771

786-
try {
787-
await channel.publish(message);
788-
console.log('Message published successfully');
789-
} catch (err) {
790-
console.log('Failed to publish message:', err.message);
791-
}
792-
```
772+
The following example manually specifies a message ID:
793773

794-
```[realtime_nodejs]
795-
const realtime = new Ably.Realtime = '{{API_KEY}}';
796-
const channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}');
797-
const message = [{ data: 'payload', id: 'unique123' }];
798774

799-
try {
800-
await channel.publish(message);
801-
console.log('Message published successfully');
802-
} catch (err) {
803-
console.log('Failed to publish message:', err.message);
804-
}
775+
```[realtime_javascript]
776+
const realtime = new Ably.Realtime = '{{API_KEY}}';
777+
const channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}');
778+
const message = [{ data: 'payload', id: 'unique123' }];
805779
```
806780

807-
```[rest_nodejs]
808-
const rest = new Ably.Rest = '{{API_KEY}}';
809-
const channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}');
810-
const message = [{ data: 'payload', id: 'unique123' }];
811-
812-
try {
813-
await channel.publish(message);
814-
console.log('Message published successfully');
815-
} catch (err) {
816-
console.log('Failed to publish message:', err.message);
817-
}
781+
```[realtime_nodejs]
782+
const realtime = new Ably.Realtime = '{{API_KEY}}';
783+
const channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}');
784+
const message = [{ data: 'payload', id: 'unique123' }];
818785
```
819786

820787
```[realtime_ruby]
821788
realtime = Ably::Realtime.new(key: '{{API_KEY}}')
822789
channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}')
823-
824-
begin
825-
channel.publish(name: 'example', data: 'payload', id: 'unique123')
826-
puts 'Realtime message published successfully'
827-
rescue => e
828-
puts "Failed to publish realtime message: #{e.message}"
829-
end
830-
```
831-
832-
```[rest_ruby]
833-
rest = Ably::Rest.new(key: '{{API_KEY}}')
834-
channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}')
835-
836-
begin
837-
channel.publish(name: 'example', data: 'payload', id: 'unique123')
838-
puts 'REST message published successfully'
839-
rescue => e
840-
puts "Failed to publish REST message: #{e.message}"
841-
end
790+
channel.publish(name: 'example', data: 'payload', id: 'unique123')
842791
```
843792

844793
```[realtime_python]
845794
realtime = AblyRealtime('{{API_KEY}}')
846795
channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}')
847796
await channel.publish([{data: 'payload', id: 'unique123'}])
848-
849-
def message_handler(message):
850-
if message.client_id in processed_messages:
851-
print("Duplicate message received.")
852-
else:
853-
print(f"Processing message: {message.data}")
854-
processed_messages.add(message.client_id)
855-
```
856-
857-
```[rest_python]
858-
rest = AblyRest('{{API_KEY}}')
859-
channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}')
860-
await channel.publish([{data: 'payload', id: 'unique123'}])
861-
862-
def message_handler(message):
863-
if message.client_id in processed_messages:
864-
print("Duplicate message received.")
865-
else:
866-
print(f"Processing message: {message.data}")
867-
processed_messages.add(message.client_id)
868-
```
869-
870-
```[rest_PHP]
871-
$rest = new Ably\AblyRest('{{API_KEY}}');
872-
$channel = $rest->channels->get('{{RANDOM_CHANNEL_NAME}}')
873-
$channel->publish([{data: 'payload', id: 'unique123'}]);
874797
```
875798

876799
```[realtime_java]
@@ -881,120 +804,31 @@ Channel channel = ably.channels.get('{{RANDOM_CHANNEL_NAME}}');
881804
Message message = new Message();
882805
message.data = "payload";
883806
message.id = "unique123";
884-
885-
channel.subscribe(msg -> {
886-
if (processedMessages.contains(msg.id)) {
887-
System.out.println("Duplicate message received.");
888-
} else {
889-
System.out.println("Processing message: " + msg.data);
890-
processedMessages.add(msg.id);
891-
}
892-
});
893-
```
894-
895-
```[rest_java]
896-
ClientOptions options = new ClientOptions('{{API_KEY}}');
897-
AblyRest ably = new AblyRest(options);
898-
Channel channel = ably.channels.get('{{RANDOM_CHANNEL_NAME}}');
899-
900-
Message message = new Message();
901-
message.data = "payload";
902-
message.id = "unique123";
903-
904-
channel.subscribe(msg -> {
905-
if (processedMessages.contains(msg.id)) {
906-
System.out.println("Duplicate message received.");
907-
} else {
908-
System.out.println("Processing message: " + msg.data);
909-
processedMessages.add(msg.id);
910-
}
911-
});
912807
```
913808

914809
```[realtime_csharp]
915810
ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:@"{{API_KEY}}"];
916811
ARTRealtimeChannel *channel = [realtime.channels get:@"{{RANDOM_CHANNEL_NAME}}"];
917-
918-
[channel publish:@"example" data:@"payload" id:@"unique123" callback:^(ARTErrorInfo *error) {
919-
if (error) {
920-
NSLog(@"Error publishing message: %@", error.message);
921-
} else {
922-
NSLog(@"Message published successfully");
923-
}
924-
}];
925-
```
926-
927-
```[rest_csharp]
928-
ARTRealtime *rest = [[ARTRealtime alloc] initWithKey:@"{{API_KEY}}"];
929-
ARTRealtimeChannel *channel = [rest.channels get:@"{{RANDOM_CHANNEL_NAME}}"];
930-
931-
[channel publish:@"example" data:@"payload" id:@"unique123" callback:^(ARTErrorInfo *error) {
932-
if (error) {
933-
NSLog(@"Error publishing message: %@", error.message);
934-
} else {
935-
NSLog(@"Message published successfully");
936-
}
937-
}];
812+
channel publish:@"example" data:@"payload" id:@"unique123" callback:^(ARTErrorInfo *error)
938813
```
939814

940815
```[realtime_swift]
941816
let realtime = ARTRealtime(key: "{{API_KEY}}")
942817
let channel = realtime.channels.get("{{RANDOM_CHANNEL_NAME}}")
943-
944-
channel.publish("example", data: "message data", id: "unique123") { error in
945-
if let error = error {
946-
print("Error publishing message: \(error)")
947-
} else {
948-
print("Message published successfully")
949-
}
950-
}
818+
channel.publish("example", data: "message data", id: "unique123")
951819
```
952820

953-
```[rest_swift]
954-
let rest = ARTRest(key: "{{API_KEY}}")
955-
var channel = rest.channels.get("{{RANDOM_CHANNEL_NAME}}")
956-
957-
channel.publish([ARTMessage(name: "example", data: "message data", id: "unique123")]) { error in
958-
if let error = error {
959-
print("Error publishing message: \(error)")
960-
} else {
961-
print("Message published successfully")
962-
}
963-
}
821+
```[realtime_objc]
822+
ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:("{{API_KEY}}"));
823+
ARTRealtimeChannel *channel = [realtime.channels get:("{{RANDOM_CHANNEL_NAME}}");
824+
[channel.publish("example", data: "message data", id: "unique123")];
964825
```
965826

966827
```[realtime_flutter]
967-
final channel = _realtime.channels.get('{{RANDOM_CHANNEL_NAME}}');
968-
await channel.publish(name: 'example', data: 'message data');
969-
970-
Future<void> initializeAndPublish(String apiKey, String channelName, String eventName, String data) async {
971-
if (_realtime == null) {
972-
_realtime = ably.Realtime(key: apiKey);
973-
}
974-
975-
if (_channel == null || _channel.name != channelName) {
976-
_channel = _realtime.channels.get(channelName);
977-
}
978-
979-
await _channel.publish(name: eventName, data: data);
980-
}
981-
```
982-
983-
```[rest_flutter]
984-
final channel = _rest.channels.get('{{RANDOM_CHANNEL_NAME}}');
985-
await channel.publish(name: 'example', data: 'message data');
986-
987-
Future<void> initializeAndPublish(String apiKey, String channelName, String eventName, String data) async {
988-
if (_rest == null) {
989-
_rest = ably.Realtime(key: apiKey);
990-
}
991-
992-
if (_channel == null || _channel.name != channelName) {
993-
_channel = _realtime.channels.get(channelName);
994-
}
995-
996-
await _channel.publish(name: eventName, data: data);
997-
}
828+
final clientOptions = ably.ClientOptions(key: '{{API_KEY}}');
829+
final rest = ably.Realtime(options: clientOptions);
830+
final channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}');
831+
final message = ably.Message(data: 'payload', id: 'unique123');
998832
```
999833

1000834
```[realtime_go]
@@ -1010,11 +844,71 @@ message := &ably.Message{
1010844
Data: "payload",
1011845
ID: "unique123",
1012846
}
847+
```
1013848

1014-
err = channel.Publish(context.Background(), "eventName", message)
1015-
if err != nil {
1016-
log.Fatalf("Error publishing message: %v", err)
1017-
}
849+
```[rest_javascript]
850+
const rest = new Ably.Rest = '{{API_KEY}}';
851+
const channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}');
852+
const message = [{ data: 'payload', id: 'unique123' }];
853+
```
854+
855+
```[rest_nodejs]
856+
const rest = new Ably.Rest = '{{API_KEY}}';
857+
const channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}');
858+
const message = [{ data: 'payload', id: 'unique123' }];
859+
```
860+
861+
```[rest_ruby]
862+
rest = Ably::Rest.new(key: '{{API_KEY}}')
863+
channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}')
864+
channel.publish(name: 'example', data: 'payload', id: 'unique123')
865+
```
866+
867+
```[rest_python]
868+
rest = AblyRest('{{API_KEY}}')
869+
channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}')
870+
await channel.publish([{data: 'payload', id: 'unique123'}])
871+
```
872+
873+
```[rest_PHP]
874+
$rest = new Ably\AblyRest('{{API_KEY}}');
875+
$channel = $rest->channels->get('{{RANDOM_CHANNEL_NAME}}')
876+
$channel->publish([{data: 'payload', id: 'unique123'}]);
877+
```
878+
879+
```[rest_java]
880+
ClientOptions options = new ClientOptions('{{API_KEY}}');
881+
AblyRest ably = new AblyRest(options);
882+
Channel channel = ably.channels.get('{{RANDOM_CHANNEL_NAME}}');
883+
884+
Message message = new Message();
885+
message.data = "payload";
886+
message.id = "unique123";
887+
```
888+
889+
```[rest_csharp]
890+
ARTRealtime *rest = [[ARTRealtime alloc] initWithKey:@"{{API_KEY}}"];
891+
ARTRealtimeChannel *channel = [rest.channels get:@"{{RANDOM_CHANNEL_NAME}}"];
892+
channel publish:@"example" data:@"payload" id:@"unique123" callback:^(ARTErrorInfo *error)
893+
```
894+
895+
```[rest_swift]
896+
let rest = ARTRest(key: "{{API_KEY}}")
897+
var channel = rest.channels.get("{{RANDOM_CHANNEL_NAME}}")
898+
channel.publish([ARTMessage(name: "example", data: "message data", id: "unique123")])
899+
```
900+
901+
```[rest_objc]
902+
ARTRest *rest = [[ARTRest alloc] initWithKey:("{{API_KEY}}"));
903+
ARTRestChannel *channel = [rest.channels get:("{{RANDOM_CHANNEL_NAME}}");
904+
[channel.publish("example", data: "message data", id: "unique123")];
905+
```
906+
907+
```[rest_flutter]
908+
final clientOptions = ably.ClientOptions(key: '{{API_KEY}}');
909+
final rest = ably.Rest(options: clientOptions);
910+
final channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}');
911+
final message = ably.Message(data: 'payload', id: 'unique123');
1018912
```
1019913

1020914
```[rest_go]
@@ -1030,14 +924,11 @@ message := &ably.Message{
1030924
Data: "payload",
1031925
ID: "unique123",
1032926
}
1033-
1034-
err = channel.Publish(context.Background(), "eventName", message)
1035-
if err != nil {
1036-
log.Fatalf("Error publishing message: %v", err)
1037-
}
1038927
```
1039928

1040-
If manually specifying message IDs, it is important to be aware of how messages are published when calling the "publish()":/api/rest-sdk/channels#publish method with an array of messages. See this "FAQ":https://faqs.ably.com/client-specified-message-id-restrictions-for-multiple-messages-published-atomically for further information.
929+
h4. Restrictions and considerations
930+
931+
When specifying custom message IDs, particularly when attempting to "publish()":/api/rest-sdk/channels#publish multiple messages atomically with idempotency, ensure that the IDs adhere to the format restrictions. For more details on these restrictions, refer to the "FAQ":https://faqs.ably.com/client-specified-message-id-restrictions-for-multiple-messages-published-atomically.
1041932

1042933
h3(#publish-on-behalf). Use the REST interface to publish on behalf of a realtime connection
1043934

0 commit comments

Comments
 (0)