This commit is contained in:
2026-08-25 14:46:06 +08:00
parent 5abc7543ae
commit f325d57fc1
106 changed files with 3682 additions and 450 deletions
@@ -0,0 +1,54 @@
package com.baiyue.eventbridge.metrics;
import com.baiyue.common.event.EventTypes;
import com.baiyue.eventbridge.mapper.BridgeInboxMapper;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
class EventBridgeInboxMetricsSamplerTest {
@Test
void samplesDatabaseOnceAndServesCachedLowCardinalityGauges() {
BridgeInboxMapper mapper = mock(BridgeInboxMapper.class);
SimpleMeterRegistry registry = new SimpleMeterRegistry();
InboxMetricsSnapshot known = snapshot("OVERSEAS", EventTypes.FIRST_LEG_LOADING_PLAN_COMPLETED,
7, 121.5, 2);
InboxMetricsSnapshot unknown = snapshot("region-123", "dynamic-event-123", 3, 900.0, 1);
when(mapper.observabilitySnapshots()).thenReturn(List.of(known, unknown));
EventBridgeInboxMetricsSampler sampler = new EventBridgeInboxMetricsSampler(mapper, registry);
sampler.sample();
verify(mapper).observabilitySnapshots();
assertThat(gauge(registry, EventBridgeInboxMetricsSampler.BACKLOG_METRIC,
"OVERSEAS", EventTypes.FIRST_LEG_LOADING_PLAN_COMPLETED)).isEqualTo(7.0);
assertThat(gauge(registry, EventBridgeInboxMetricsSampler.OLDEST_AGE_METRIC,
"OVERSEAS", EventTypes.FIRST_LEG_LOADING_PLAN_COMPLETED)).isEqualTo(121.5);
assertThat(gauge(registry, EventBridgeInboxMetricsSampler.DEAD_METRIC,
"OTHER", "OTHER")).isEqualTo(1.0);
verifyNoMoreInteractions(mapper);
}
private InboxMetricsSnapshot snapshot(String region, String eventType, long backlog,
double oldestAge, long dead) {
InboxMetricsSnapshot snapshot = new InboxMetricsSnapshot();
snapshot.setTargetRegion(region);
snapshot.setEventType(eventType);
snapshot.setBacklogCount(backlog);
snapshot.setOldestPendingAgeSeconds(oldestAge);
snapshot.setDeadCount(dead);
return snapshot;
}
private double gauge(SimpleMeterRegistry registry, String name, String region, String eventType) {
return registry.get(name).tags("target_region", region, "event_type", eventType).gauge().value();
}
}
@@ -0,0 +1,58 @@
package com.baiyue.eventbridge.metrics;
import com.baiyue.eventbridge.domain.BridgeInbox;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.core.instrument.MeterRegistry;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.mockito.Mockito.mock;
class EventBridgeMetricsTest {
@Test
void collapsesUnboundedRegionAndEventTypeLabelsToOther() {
SimpleMeterRegistry registry = new SimpleMeterRegistry();
EventBridgeMetrics metrics = new EventBridgeMetrics(registry);
BridgeInbox event = new BridgeInbox();
event.setTargetRegion("customer-controlled-region");
event.setEventType("dynamic-event-123456");
metrics.recordTransport(event, EventBridgeMetrics.TransportResult.RETRY);
assertThat(registry.get(EventBridgeMetrics.TRANSPORT_METRIC)
.tags("target_region", "OTHER", "event_type", "OTHER", "result", "RETRY")
.counter().count()).isEqualTo(1.0);
assertThat(registry.getMeters()).allSatisfy(meter -> assertThat(meter.getId().getTags())
.noneMatch(tag -> tag.getValue().contains("dynamic-event")));
assertThat(EventBridgeMetrics.eventType(null)).isEqualTo("OTHER");
}
@Test
void everyFrozenEventTypeHasABoundedTagValue() {
assertThat(Arrays.stream(com.baiyue.common.event.EventTypes.class.getFields())
.filter(field -> Modifier.isStatic(field.getModifiers()) && field.getType() == String.class)
.map(field -> {
try {
return (String) field.get(null);
} catch (IllegalAccessException exception) {
throw new AssertionError(exception);
}
})).allMatch(value -> EventBridgeMetrics.eventType(value).equals(value));
}
@Test
void metricsFailureDoesNotChangeTransportControlFlow() {
EventBridgeMetrics metrics = new EventBridgeMetrics(mock(MeterRegistry.class));
BridgeInbox event = new BridgeInbox();
event.setTargetRegion("CN");
event.setEventType(com.baiyue.common.event.EventTypes.OVERSEAS_CONTAINER_UNLOADED);
assertThatCode(() -> metrics.recordTransport(event, EventBridgeMetrics.TransportResult.RETRY))
.doesNotThrowAnyException();
}
}
@@ -0,0 +1,41 @@
package com.baiyue.eventbridge.metrics;
import com.baiyue.eventbridge.mapper.BridgeInboxMapper;
import org.apache.ibatis.annotations.Select;
import org.junit.jupiter.api.Test;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
class EventBridgeObservabilityContractTest {
@Test
void aggregationIsScheduledCacheInputAndUsesExistingStatusIndexBasis() throws Exception {
Select annotation = BridgeInboxMapper.class.getMethod("observabilitySnapshots")
.getAnnotation(Select.class);
String query = String.join(" ", annotation.value()).replaceAll("\\s+", " ");
String schema = Files.readString(Path.of("src/main/resources/db/event-bridge.sql"));
assertThat(query)
.contains("WHERE status IN ('RECEIVED','RETRY','PROCESSING','DEAD')")
.contains("GROUP BY target_region, event_type")
.doesNotContain("event_id AS", "customer_id AS", "last_error AS");
assertThat(schema).contains(
"KEY idx_bridge_inbox_consume (status, next_retry_at, lease_until)");
}
@Test
void developmentSloAndAlertThresholdsAreFrozenWithoutDeploymentClaim() throws Exception {
String contract = Files.readString(Path.of("docs/development-observability-slo.md"));
assertThat(contract)
.contains("99% 的新接收跨区事件在 60 秒内")
.contains("99.9% 的新接收跨区事件在 15 分钟内")
.contains("result=\"DEAD\"", "> 120", "> 900")
.contains("不表示任何环境已经部署")
.contains("idx_bridge_inbox_consume(status, next_retry_at, lease_until)")
.doesNotContain("customer_id\"", "event_id\"");
}
}
@@ -5,7 +5,9 @@ import com.baiyue.common.event.EventPayloadCanonicalizer;
import com.baiyue.common.event.EventPayloadHash;
import com.baiyue.common.event.RegionCode;
import com.baiyue.eventbridge.domain.BridgeInbox;
import com.baiyue.eventbridge.metrics.EventBridgeMetrics;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.Test;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.test.util.ReflectionTestUtils;
@@ -24,6 +26,8 @@ import static org.mockito.Mockito.when;
class EventInboxServiceTest {
private final ObjectMapper objectMapper = new ObjectMapper();
private final InboxStore store = mock(InboxStore.class);
private final SimpleMeterRegistry registry = new SimpleMeterRegistry();
private final EventBridgeMetrics metrics = new EventBridgeMetrics(registry);
private final EventInboxService service = service("OVERSEAS");
@Test
@@ -33,6 +37,7 @@ class EventInboxServiceTest {
assertThat(service.receive(envelope).result()).isEqualTo("已接收");
verify(store).insert(any(BridgeInbox.class));
assertThat(receiveCount("RECEIVED")).isEqualTo(1.0);
}
@Test
@@ -57,6 +62,7 @@ class EventInboxServiceTest {
when(store.findByEventId(envelope.eventId())).thenReturn(existing);
assertThat(service.receive(envelope).result()).isEqualTo("重复");
assertThat(receiveCount("DUPLICATE")).isEqualTo(1.0);
}
@Test
@@ -67,6 +73,7 @@ class EventInboxServiceTest {
assertThatThrownBy(() -> service.receive(envelope))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("拒绝覆盖");
assertThat(receiveCount("CONFLICT")).isEqualTo(1.0);
}
@Test
@@ -113,10 +120,17 @@ class EventInboxServiceTest {
EventInboxService service = new EventInboxService();
ReflectionTestUtils.setField(service, "inboxStore", store);
ReflectionTestUtils.setField(service, "objectMapper", objectMapper);
ReflectionTestUtils.setField(service, "metrics", metrics);
ReflectionTestUtils.setField(service, "localRegion", region);
return service;
}
private double receiveCount(String result) {
return registry.get(EventBridgeMetrics.RECEIVE_METRIC)
.tags("target_region", "OVERSEAS", "event_type", "OrderApproved", "result", result)
.counter().count();
}
private EventEnvelope withHash(EventEnvelope source) {
String hash = EventPayloadHash.sha256(EventPayloadCanonicalizer.canonicalize(source.payload()));
return new EventEnvelope(source.eventId(), source.eventType(), source.eventVersion(), source.occurredAt(),
@@ -1,6 +1,11 @@
package com.baiyue.eventbridge.service;
import com.baiyue.eventbridge.domain.BridgeDeliveryAttempt;
import com.baiyue.eventbridge.domain.BridgeInbox;
import com.baiyue.eventbridge.mapper.BridgeDeliveryAttemptMapper;
import com.baiyue.eventbridge.mapper.BridgeInboxMapper;
import com.baiyue.eventbridge.metrics.EventBridgeMetrics;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import org.apache.ibatis.annotations.Update;
@@ -11,15 +16,31 @@ import static org.mockito.Mockito.*;
class EventReplayServiceTest {
private final BridgeInboxMapper mapper = mock(BridgeInboxMapper.class);
private final BridgeDeliveryAttemptMapper attemptMapper = mock(BridgeDeliveryAttemptMapper.class);
private final SimpleMeterRegistry registry = new SimpleMeterRegistry();
private final EventBridgeMetrics metrics = new EventBridgeMetrics(registry);
private final EventReplayService service = service();
@Test
void requeuesDeadEventWithAuditReason() {
when(mapper.replayDead(eq("event-1"), contains("incident-42"))).thenReturn(1);
BridgeInbox event = new BridgeInbox();
event.setEventId("event-1"); event.setSourceRegion("CN"); event.setTargetRegion("OVERSEAS");
event.setEventType(com.baiyue.common.event.EventTypes.FIRST_LEG_LOADING_PLAN_COMPLETED);
when(mapper.selectById("event-1")).thenReturn(event);
when(mapper.nextAttemptNo("event-1")).thenReturn(4);
service.replayDead("event-1", "operator-7", "incident-42 approved");
verify(mapper).replayDead(eq("event-1"), contains("operator-7"));
var captor = org.mockito.ArgumentCaptor.forClass(BridgeDeliveryAttempt.class);
verify(attemptMapper).insert(captor.capture());
assertThat(captor.getValue().getAttemptNo()).isEqualTo(4);
assertThat(captor.getValue().getStatus()).isEqualTo("MANUAL_REPLAY");
assertThat(captor.getValue().getErrorMessage()).contains("operator-7", "incident-42");
assertThat(registry.get(EventBridgeMetrics.TRANSPORT_METRIC)
.tags("target_region", "OVERSEAS", "event_type", event.getEventType(),
"result", "MANUAL_REPLAY").counter().count()).isEqualTo(1.0);
}
@Test
@@ -43,6 +64,8 @@ class EventReplayServiceTest {
private EventReplayService service() {
EventReplayService service = new EventReplayService();
ReflectionTestUtils.setField(service, "inboxMapper", mapper);
ReflectionTestUtils.setField(service, "attemptMapper", attemptMapper);
ReflectionTestUtils.setField(service, "metrics", metrics);
return service;
}
}
@@ -0,0 +1,26 @@
package com.baiyue.eventbridge.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class HttpFirstlegEventClientTest {
private final ObjectMapper objectMapper = new ObjectMapper();
@Test
void businessFailureIsDiagnosableAndMustNotBeTreatedAsAcknowledged() throws Exception {
var body = objectMapper.readTree("""
{"code":"A0409","message":"事件摘要冲突","requestId":"request-cn-1","traceId":"trace-cn-1"}
""");
assertThat(HttpFirstlegEventClient.failureMessage(200, body))
.isEqualTo("头程消费失败: HTTP 200, code=A0409, message=事件摘要冲突, requestId=request-cn-1, traceId=trace-cn-1");
}
@Test
void nonJsonFailureIsStillDiagnosableWithoutRawBody() {
assertThat(HttpFirstlegEventClient.failureMessage(503, null))
.isEqualTo("头程消费失败: HTTP 503, code=missing, message=non-json response, requestId=missing, traceId=missing");
}
}
@@ -1,10 +1,13 @@
package com.baiyue.eventbridge.service;
import com.baiyue.common.event.EventTypes;
import com.baiyue.eventbridge.config.EventDispatcherProperties;
import com.baiyue.eventbridge.domain.BridgeDeliveryAttempt;
import com.baiyue.eventbridge.domain.BridgeInbox;
import com.baiyue.eventbridge.mapper.BridgeDeliveryAttemptMapper;
import com.baiyue.eventbridge.mapper.BridgeInboxMapper;
import com.baiyue.eventbridge.metrics.EventBridgeMetrics;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
@@ -12,6 +15,7 @@ import org.springframework.test.util.ReflectionTestUtils;
import java.net.URI;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@@ -21,10 +25,14 @@ import static org.mockito.Mockito.*;
class OverseasEventDispatcherTest {
private final BridgeInboxMapper inboxMapper = mock(BridgeInboxMapper.class);
private final BridgeDeliveryAttemptMapper attemptMapper = mock(BridgeDeliveryAttemptMapper.class);
private final LastlegEventClient eventClient = mock(LastlegEventClient.class);
private final FirstlegEventClient firstlegEventClient = mock(FirstlegEventClient.class);
private final com.baiyue.eventbridge.service.LastlegEventClient eventClient =
mock(com.baiyue.eventbridge.service.LastlegEventClient.class);
private final com.baiyue.eventbridge.service.FirstlegEventClient firstlegEventClient =
mock(com.baiyue.eventbridge.service.FirstlegEventClient.class);
private final SecurityProjectionEventClient securityProjectionEventClient = mock(SecurityProjectionEventClient.class);
private OverseasEventDispatcher dispatcher;
private BridgeInbox event;
private SimpleMeterRegistry registry;
@BeforeEach
void setUp() {
@@ -33,16 +41,21 @@ class OverseasEventDispatcherTest {
Duration.ofSeconds(30), Duration.ofSeconds(5), Duration.ofMinutes(1),
Duration.ofSeconds(3), Duration.ofSeconds(10));
dispatcher = new OverseasEventDispatcher();
registry = new SimpleMeterRegistry();
ReflectionTestUtils.setField(dispatcher, "inboxMapper", inboxMapper);
ReflectionTestUtils.setField(dispatcher, "attemptMapper", attemptMapper);
ReflectionTestUtils.setField(dispatcher, "eventClient", eventClient);
ReflectionTestUtils.setField(dispatcher, "firstlegEventClient", firstlegEventClient);
ReflectionTestUtils.setField(dispatcher, "securityProjectionEventClient", securityProjectionEventClient);
ReflectionTestUtils.setField(dispatcher, "properties", properties);
ReflectionTestUtils.setField(dispatcher, "metrics", new EventBridgeMetrics(registry));
event = new BridgeInbox();
event.setEventId("event-1");
event.setEventType(EventTypes.FIRST_LEG_LOADING_PLAN_COMPLETED);
event.setSourceRegion("CN");
event.setTargetRegion("OVERSEAS");
event.setRetryCount(0);
event.setReceivedAt(LocalDateTime.now().minusSeconds(5));
when(inboxMapper.claim(eq("event-1"), anyString(), anyLong())).thenReturn(1);
when(inboxMapper.selectById("event-1")).thenReturn(event);
when(inboxMapper.nextAttemptNo("event-1")).thenReturn(1);
@@ -56,27 +69,50 @@ class OverseasEventDispatcherTest {
dispatcher.dispatchOne("event-1");
verify(inboxMapper).markProcessed(eq("event-1"), anyString());
verify(eventClient).deliver(event);
verifyNoInteractions(securityProjectionEventClient);
assertThat(updatedAttempt().getStatus()).isEqualTo("DELIVERED");
assertThat(transportCount("DELIVERED")).isEqualTo(1.0);
assertThat(registry.get(EventBridgeMetrics.DELIVERY_LATENCY_METRIC).timer().count()).isEqualTo(1);
}
@Test
void failedDeliveryIsScheduledForRetry() throws Exception {
when(eventClient.deliver(event)).thenThrow(new IllegalStateException("network down"));
when(inboxMapper.markFailed(eq("event-1"), anyString(), eq(3), anyLong(), eq("network down")))
.thenReturn(1);
dispatcher.dispatchOne("event-1");
verify(inboxMapper).markFailed(eq("event-1"), anyString(), eq(3), anyLong(), eq("network down"));
verify(inboxMapper).markFailed(eq("event-1"), anyString(), eq(3), eq(5L), eq("network down"));
assertThat(updatedAttempt().getStatus()).isEqualTo("RETRY");
assertThat(transportCount("RETRY")).isEqualTo(1.0);
}
@Test
void finalFailureIsMovedToDeadState() throws Exception {
event.setRetryCount(2);
when(eventClient.deliver(event)).thenThrow(new IllegalStateException("still down"));
when(inboxMapper.markFailed(eq("event-1"), anyString(), eq(3), anyLong(), eq("still down")))
.thenReturn(1);
dispatcher.dispatchOne("event-1");
verify(inboxMapper).markFailed(eq("event-1"), anyString(), eq(3), eq(20L), eq("still down"));
assertThat(updatedAttempt().getStatus()).isEqualTo("DEAD");
assertThat(transportCount("DEAD")).isEqualTo(1.0);
}
@Test
void exponentialBackoffIsCappedAtConfiguredMaximum() throws Exception {
event.setRetryCount(4);
when(eventClient.deliver(event)).thenThrow(new IllegalStateException("still down"));
when(inboxMapper.markFailed(eq("event-1"), anyString(), eq(3), anyLong(), eq("still down")))
.thenReturn(1);
dispatcher.dispatchOne("event-1");
verify(inboxMapper).markFailed(eq("event-1"), anyString(), eq(3), eq(60L), eq("still down"));
}
@Test
@@ -92,6 +128,7 @@ class OverseasEventDispatcherTest {
@Test
void cnTargetIsDeliveredToFirstleg() throws Exception {
event.setEventType(EventTypes.OVERSEAS_CONTAINER_UNLOADED);
event.setSourceRegion("OVERSEAS");
event.setTargetRegion("CN");
when(firstlegEventClient.deliver(event)).thenReturn("200");
@@ -101,9 +138,87 @@ class OverseasEventDispatcherTest {
verify(firstlegEventClient).deliver(event);
verifyNoInteractions(eventClient);
verifyNoInteractions(securityProjectionEventClient);
assertThat(updatedAttempt().getStatus()).isEqualTo("DELIVERED");
}
@Test
void iamSecurityEventsAreDeliveredToSecurityProjectionAndNeverLastleg() throws Exception {
for (String eventType : List.of(EventTypes.IAM_USER_STATUS_CHANGED,
EventTypes.IAM_TENANT_STATUS_CHANGED, EventTypes.IAM_PERMISSION_VERSION_CHANGED)) {
reset(inboxMapper, attemptMapper, eventClient, firstlegEventClient, securityProjectionEventClient);
event.setEventType(eventType);
when(inboxMapper.claim(eq("event-1"), anyString(), anyLong())).thenReturn(1);
when(inboxMapper.selectById("event-1")).thenReturn(event);
when(inboxMapper.nextAttemptNo("event-1")).thenReturn(1);
when(inboxMapper.markProcessed(eq("event-1"), anyString())).thenReturn(1);
when(securityProjectionEventClient.deliver(event)).thenReturn("200");
dispatcher.dispatchOne("event-1");
verify(securityProjectionEventClient).deliver(event);
verifyNoInteractions(eventClient);
verifyNoInteractions(firstlegEventClient);
}
}
@Test
void unsupportedTargetRegionIsNeverMisroutedToLastleg() {
event.setTargetRegion("UNKNOWN");
when(inboxMapper.markFailed(eq("event-1"), anyString(), eq(3), anyLong(), contains("不支持")))
.thenReturn(1);
dispatcher.dispatchOne("event-1");
verifyNoInteractions(eventClient);
verifyNoInteractions(firstlegEventClient);
verifyNoInteractions(securityProjectionEventClient);
assertThat(updatedAttempt().getStatus()).isEqualTo("RETRY");
}
@Test
void missingEventTypeFailsWithExplicitDiagnosticAndIsNeverDelivered() {
event.setEventType(null);
when(inboxMapper.markFailed(eq("event-1"), anyString(), eq(3), anyLong(),
eq("事件类型不能为空,无法选择下游消费者"))).thenReturn(1);
dispatcher.dispatchOne("event-1");
verifyNoInteractions(eventClient);
verifyNoInteractions(firstlegEventClient);
verifyNoInteractions(securityProjectionEventClient);
BridgeDeliveryAttempt attempt = updatedAttempt();
assertThat(attempt.getStatus()).isEqualTo("RETRY");
assertThat(attempt.getErrorMessage()).isEqualTo("事件类型不能为空,无法选择下游消费者");
}
@Test
void acknowledgedDeliveryWithLostLeaseIsNotRecordedAsRetryOrDead() throws Exception {
when(eventClient.deliver(event)).thenReturn("200");
when(inboxMapper.markProcessed(eq("event-1"), anyString())).thenReturn(0);
dispatcher.dispatchOne("event-1");
verify(inboxMapper, never()).markFailed(anyString(), anyString(), anyInt(), anyLong(), anyString());
BridgeDeliveryAttempt attempt = updatedAttempt();
assertThat(attempt.getStatus()).isEqualTo("ACKNOWLEDGED_LEASE_LOST");
assertThat(attempt.getResponseCode()).isEqualTo("200");
assertThat(attempt.getErrorMessage()).contains("租约已失效");
assertThat(transportCount("ACKNOWLEDGED_LEASE_LOST")).isEqualTo(1.0);
}
@Test
void failedDeliveryAfterLeaseTakeoverDoesNotOverwriteNewOwnerState() throws Exception {
when(eventClient.deliver(event)).thenThrow(new IllegalStateException("network down"));
when(inboxMapper.markFailed(eq("event-1"), anyString(), eq(3), anyLong(), eq("network down")))
.thenReturn(0);
dispatcher.dispatchOne("event-1");
assertThat(updatedAttempt().getStatus()).isEqualTo("FAILED_LEASE_LOST");
assertThat(transportCount("FAILED_LEASE_LOST")).isEqualTo(1.0);
}
@Test
void downstream503RetriesThenDeliversWithDiagnosticAttemptsInBothDirections() throws Exception {
for (String target : List.of("OVERSEAS", "CN")) {
@@ -162,4 +277,11 @@ class OverseasEventDispatcherTest {
verify(attemptMapper).updateById(captor.capture());
return captor.getValue();
}
private double transportCount(String result) {
return registry.get(EventBridgeMetrics.TRANSPORT_METRIC)
.tags("target_region", event.getTargetRegion(),
"event_type", event.getEventType(), "result", result)
.counter().count();
}
}