-
Notifications
You must be signed in to change notification settings - Fork 0
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
Dev/feilkoe headers #112
base: main
Are you sure you want to change the base?
Dev/feilkoe headers #112
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.
Noen kommentarer elles ser jo dette ut som en god start 👍
): Route = | ||
get("/api/retry/{$RETRY_LIMIT}") { | ||
resourceScope { | ||
CoroutineScope(Dispatchers.IO).launch { |
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.
Når du oppretter et CoroutineScope
her på denne måten er det greit å være klar over at dette blir et helt frittstående scope som ikke er linket til resourceScope
'et. Joda, det vil bli tatt ned når jvm'en shutter ned, men ikke på en clean måte.
Jeg snakket jo litt om dette for noen uker siden (i en delingstime) hvordan en kan linke frittstående scopes til omsluttende ressurs-scope så scopet i seg selv blir en ressurs / avhengighet på lik linje med alle andre ressurser og avhengigheter. Fordelen er selvsagt at scopet blir inkludert i livssyklusen til ressurs-scopet i SuspendApp
og tatt ned og opprettet på en clean måte.
Her er linken til utility funksjonen som gjør dette for deg: https://github.com/navikt/smtp-transport/blob/main/src/main/kotlin/no/nav/emottak/util/ResourceUtil.kt
Sikkert noe vi burde putte inn i felles-biblioteket på et tidspunkt.
|
||
fun Route.simulateError(): Route = get("/api/forceretry/{$KAFKA_OFFSET}") { | ||
resourceScope { | ||
CoroutineScope(Dispatchers.IO).launch { |
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.
Samme som over
if (config().kafkaErrorQueue.active) { | ||
failedMessageQueue.receive( | ||
payloadMessageProcessorProvider.invoke(), | ||
limit = (call.parameters[RETRY_LIMIT] as String).toInt() |
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.
Litt flisespikkeri, men syntes koden blir mer ryddig hvis du løfter call...
ut til en egen val
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.
Hvorfor synes du det?
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.
Tipper fordi blir koden mer verbose. I prinsipp er jeg helt enig, men akkurat i dette eksemplet tenker jeg at det ikke gir så mye verdi å bruke mer tid på denne linjen
suspend fun send(record: ReceiverRecord<String, ByteArray>, key: String = record.key(), value: ByteArray = record.value()) { | ||
record.addHeader(RETRY_AFTER, getNextRetryTime(record)) | ||
try { | ||
val result = producer.send(ProducerRecord(kafkaErrorQueue.topic, null, key, value, record.headers())).get() |
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.
Jeg tenker at her bør du bruke KafkaPublisher
fra kotlin-kafka
-biblioteket så får du et publisherScope
ut av boksen. Dette vil forenkle og gjøre koden mer tydelig.
|
||
fun getNextRetryTime(record: ReceiverRecord<String, ByteArray>): String { | ||
return DateTime.now().plusMinutes(5) | ||
.toString() // TODO create retry strategy |
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.
Ref retry strategi, kanskje du bør se litt på https://arrow-kt.io/learn/resilience/retry-and-repeat/
ByteArrayDeserializer() | ||
) | ||
) { | ||
partitionsFor(topic) |
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.
Det er mye som foregår her, hva med å splitte dette opp i et par mindre funksjoner for å tydeliggjøre hva som faktisk skjer
} | ||
} | ||
|
||
fun getReceiverRecord(consumerRecord: ConsumerRecord<String, ByteArray>?): ReceiverRecord<String, ByteArray>? { |
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.
Det blir veldig mye kode inne i denne funksjonen og det går utover lesbarheten. Kan du ikke trekke klassen ut ?
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.
Skjønner hva du mener, om man bare er interessert i flyten er det ikke interessant eller nødvendig med støyen en plutselig klassedefinisjon skaper men jeg har en annen vinkling.
Klassen eksisterer kun i denne contexten og trenger ikke eksistere i annet scope, ved å flytte den "forsøpler" den lesbarheten til resten av koden "forøvrig". Ved å se den her vet man at den kun eksisterer her umiddelbart. Ved å se den frittstående vil man tro den kan brukes flere steder og har et "udefinert eksistensområde" i koden. Det kan være vel så kognitivt belastende for en leser.
import kotlin.io.path.Path | ||
import kotlin.io.path.exists | ||
|
||
class KafkaIntegrationTest { |
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.
Veldig tynn integrasjonstest. savner feil-scenarioer
} | ||
} | ||
if (!config().kafkaErrorQueue.active) { | ||
call.respondText(status = HttpStatusCode.ServiceUnavailable, text = "Retry not active.") |
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.
Selv om Retry i teksten her refererer til API-endepunktet, kan teksten lett misforstås. Kanskje det er bedre å tydeliggjøre at det er endepunktet som ikke er aktivt, i stedet for å bare kalle det "Retry"
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.
Det er faktisk retry som er inaktivt, ikke endepunktet. ;)
Altså kafka-køen er ikke aktiv. Så man kan ikke få gjort noe retrys.
keySerializer = StringSerializer(), | ||
valueSerializer = ByteArraySerializer(), | ||
acknowledgments = Acks.All, | ||
properties = kafka.toProperties() | ||
) |
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.
Dette er en stor forbering! 👍 ⚡
record: ReceiverRecord<String, ByteArray>, | ||
key: String = record.key(), | ||
value: ByteArray = record.value() | ||
) { | ||
record.addHeader(RETRY_AFTER, getNextRetryTime(record)) | ||
try { |
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.
Nå som jeg tenker på det - hva sier du til at vi bruker Arrow her også, så vi slipper de litt kjipe try-catchene?
suspend fun consumeRetryQueue( // TODO refine retry logic | ||
payloadMessageProcessor: PayloadMessageProcessor, | ||
limit: Int = 10 // TODO default limit to offset | ||
) { | ||
// TODO DefaultKafkaReceiver is too constrainted so need own impl for custom logic | ||
val consumer: Flow<ReceiverRecord<String, ByteArray>> = | ||
errorTopicKafkaReceiver.receive(kafkaErrorQueue.topic) | ||
|
||
logger.debug("Reading from error queue") | ||
var counter = 0 | ||
consumerFlow.map { record -> | ||
consumer.map { record -> | ||
counter++ | ||
if (counter > limit) { | ||
throw Exception("Error queue limit exceeded: $limit") // TODO fjern dette | ||
logger.info("Kafka retryQueue Limit reached: $limit") | ||
return@map | ||
} | ||
record.offset.acknowledge() | ||
record.retryCounter() | ||
payloadMessageProcessor.process(record) | ||
record.offset.acknowledge() | ||
if (DateTime.parse( | ||
String(record.headers().lastHeader(RETRY_AFTER).value()) | ||
).isAfter(DateTime.now()) | ||
) { | ||
payloadMessageProcessor.process(record) | ||
} else { | ||
logger.info("${record.key()} is not retryable yet.") | ||
failedMessageQueue.sendToRetry(record) | ||
} | ||
record.offset.commit() | ||
}.collect() | ||
} |
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.
Siden vi vet at denne delen trenger mer arbeid, hva tenker du om å legge til en enkel unit test som kan hjelpe oss å oppdage hvis vi ubevisst ødelegger noe etter refaktorering? Det ville gjort det mye tryggere å rydde opp her, og samtidig gjøre terskelen til å rydde opp her lavere
@@ -26,10 +29,15 @@ class KafkaIntegrationTest { | |||
val kafkaConfig = config() | |||
|
|||
fun noLocalKafkaEnv(): Boolean { |
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.
En liten forenkling du kan vurdere her: Hva med å kalle denne hasLocalKafkaEnv
i stedet for noLocalKafkaEnv
? Det gjør det litt enklere å lese, siden vi slipper å tenke i det negative. 😅
suspend fun receive(payloadMessageProcessor: PayloadMessageProcessor, limit: Int = 10) { // TODO limit til offset | ||
suspend fun consumeRetryQueue( // TODO refine retry logic | ||
payloadMessageProcessor: PayloadMessageProcessor, | ||
limit: Int = 10 // TODO default limit to 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.
// TODO default limit to offset
jeg forstår ikke denne kommentaren. Kan vi forbedre den eller ta den vekk hvis du ikke heller gjør det?
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.
Tenkte limiten er siste offset på tidspunktet man starter processen. Men man kan ikke innhente siste offset vha kotlin-kafka sin DefaultKafkaReceiver. Det blir en større jobb isåfall som er litt tentativ. Så lagde TODO
Mer kan selvsagt gjøres men tenker det er nok foreløpig. Har testet det i dev vha. retry endepunktet |
No description provided.