-
Notifications
You must be signed in to change notification settings - Fork 20
feat: improve publish endpoint response #563
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
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
I'm not a fan of the semantics of those responses. First I think the key is not explicit/descriptive enough like |
|
hmm I see your point. How about queued_count and if the event is already processed then we can have "duplicate" or something like that? |
|
Why not just something like this? {
"id": "evt_123",
"duplicate": true // Or false if it's not duplicate.
} |
🤯 |
1cefe78 to
5a4e4ef
Compare
|
@claude please review |
|
Claude finished @alexluong's task —— View job Code ReviewOverall AssessmentThis PR successfully implements detailed ingestion status feedback with a clean architecture. The implementation is well-tested and correctly handles the idempotency strategy described in the PR. However, I recommend considering the feedback from @fmvilas and @alexbouchardd about using a simpler Key Strengths:
Areas for Improvement:
1. API Response Design - Consider SimplificationThe current implementation uses {
"id": "evt_123",
"duplicate": true
}Current implementation analysis:
Recommendation:
If keeping the current approach, I'd recommend renaming Files affected:
2. Code Quality - matchSpecificDestination FunctionLocation: The destination, err := h.entityStore.RetrieveDestination(ctx, event.TenantID, event.DestinationID)
if err != nil {
h.logger.Ctx(ctx).Warn("failed to retrieve destination",
zap.Error(err),
zap.String("destination_id", event.DestinationID))
status := DestinationStatusNotFound
return []models.DestinationSummary{}, &status, nil
}
if destination == nil {
status := DestinationStatusNotFound
return []models.DestinationSummary{}, &status, nil
}Issue: Both error and nil cases return the same status. These could be combined: destination, err := h.entityStore.RetrieveDestination(ctx, event.TenantID, event.DestinationID)
if err != nil || destination == nil {
if err != nil {
h.logger.Ctx(ctx).Warn("failed to retrieve destination",
zap.Error(err),
zap.String("destination_id", event.DestinationID))
}
status := DestinationStatusNotFound
return []models.DestinationSummary{}, &status, nil
}3. Topic Validation Logic - Potential Edge CaseLocation: if event.Topic != "" && event.Topic != "*" && destination.Topics[0] != "*" && !slices.Contains(destination.Topics, event.Topic) {Issue: This line accesses Risk: If a destination somehow has an empty Recommendation: Add a length check: if event.Topic != "" && event.Topic != "*" &&
len(destination.Topics) > 0 && destination.Topics[0] != "*" &&
!slices.Contains(destination.Topics, event.Topic) {Alternatively, since if event.Topic != "" && event.Topic != "*" &&
!slices.Contains(destination.Topics, "*") &&
!slices.Contains(destination.Topics, event.Topic) {4. Test Coverage - Good but Missing One ScenarioThe test coverage in Missing test: "duplicate event with This would verify that when:
This is an important scenario since the idempotency logic is shared between topic-based and destination-specific paths. 5. Documentation - DestinationStatus UsageLocation: The
Recommendation: Add a comment to the struct field explaining when it's populated: type HandleResult struct {
EventID string `json:"id"`
Duplicate bool `json:"duplicate"`
// DestinationStatus is only populated when destination_id is specified
// AND the delivery was not queued (e.g., disabled, not_found, topic_mismatch)
DestinationStatus *DestinationMatchStatus `json:"destination_status,omitempty"`
}6. Positive ObservationsExcellent idempotency architecture:
Comprehensive test scenarios:
Proper error handling:
SummaryThe implementation is solid and well-tested. My main recommendation is to consider the team's feedback about using a simpler If you decide to keep the current approach, please address:
|
* test: event with destination_id should not match disabled destination * fix: check disabled * feat: enhane publish handler with more result info * refactor: match event logic with destination_id field * test: remove parallel from flaky tests * test: extend timeout to avoid flaky test * refactor: api response * test: use fast timeout to avoid flaky tests * test: retry poll timeout * refactor: simplify API response to use duplicate boolean * refactor: topics.matchtopic util * test: duplicate publish * chore: simplify publish response * docs: openapi.yaml & api.mdx
Summary
Returns detailed ingestion status showing what happened with each event, including idempotency detection.
API Response Changes
Before
{ "id": "evt_123" }After
{ "id": "evt_123", "duplicate": false }Response Fields
id: The event ID (provided in request or server-generated)duplicate: Whether this event was already processed (idempotency hit)Detecting Idempotency
Fresh request:
duplicate == falseDuplicate:
duplicate == trueStill processing: HTTP
409 ConflictExamples
Idempotency Strategy
Matching OUTSIDE idempotency → fresh status every request
Publishing INSIDE idempotency → single key per event
Original PR Description (for reference)
Summary
Returns detailed ingestion status showing what happened with each event, including idempotency detection and destination-specific feedback.
API Response Changes
Before
{ "id": "evt_123" }After
{ "id": "evt_123", "matched_count": 3, "queued_count": 3 }Response Fields
matched_count: Destinations that matched the eventqueued_count: Deliveries actually queued this requestdestination_status: Status string (only whendestination_idspecified AND nothing queued)Detecting Idempotency
Fresh request:
matched_count == queued_count(both non-zero)Duplicate:
matched_count > 0butqueued_count == 0Still processing: HTTP
409 ConflictExamples
Idempotency Strategy
Matching OUTSIDE idempotency → fresh status every request
Publishing INSIDE idempotency → single key per event