feat: Producerにタスク完了を通知する方法を追加
This commit is contained in:
		
							parent
							
								
									dab52d25a4
								
							
						
					
					
						commit
						e9277db29f
					
				|  | @ -73,6 +73,19 @@ class MongodbTaskRepository(database: MongoDatabase, private val propertySeriali | |||
|     override suspend fun findById(uuid: UUID): Task? = withContext(Dispatchers.IO) { | ||||
|         collection.find(Filters.eq(uuid.toString())).singleOrNull()?.toTask(propertySerializerFactory) | ||||
|     } | ||||
| 
 | ||||
|     override suspend fun findByIdAndUpdate(id: UUID, task: Task) { | ||||
|         collection.replaceOne( | ||||
|             Filters.eq("_id", task.id.toString()), TaskMongodb.of(propertySerializerFactory, task), | ||||
|             ReplaceOptions().upsert(false) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     override suspend fun findByPublishProducerIdAndCompletedAtIsNotNull(publishProducerId: UUID): Flow<Task> { | ||||
|         return collection | ||||
|             .find(Filters.eq(TaskMongodb::publishProducerId.name, publishProducerId.toString())) | ||||
|             .map { it.toTask(propertySerializerFactory) } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| data class TaskMongodb( | ||||
|  |  | |||
|  | @ -0,0 +1,93 @@ | |||
| /* | ||||
|  * Copyright (C) 2024 usbharu | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package dev.usbharu.owl.broker.mongodb | ||||
| 
 | ||||
| import com.mongodb.client.model.Filters | ||||
| import com.mongodb.client.model.ReplaceOptions | ||||
| import com.mongodb.kotlin.client.coroutine.MongoDatabase | ||||
| import dev.usbharu.owl.broker.domain.model.taskresult.TaskResult | ||||
| import dev.usbharu.owl.broker.domain.model.taskresult.TaskResultRepository | ||||
| import dev.usbharu.owl.common.property.PropertySerializeUtils | ||||
| import dev.usbharu.owl.common.property.PropertySerializerFactory | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| import kotlinx.coroutines.flow.flowOn | ||||
| import kotlinx.coroutines.flow.map | ||||
| import kotlinx.coroutines.withContext | ||||
| import org.bson.BsonType | ||||
| import org.bson.codecs.pojo.annotations.BsonId | ||||
| import org.bson.codecs.pojo.annotations.BsonRepresentation | ||||
| import org.koin.core.annotation.Singleton | ||||
| import java.util.* | ||||
| 
 | ||||
| @Singleton | ||||
| class MongodbTaskResultRepository( | ||||
|     database: MongoDatabase, | ||||
|     private val propertySerializerFactory: PropertySerializerFactory | ||||
| ) : TaskResultRepository { | ||||
| 
 | ||||
|     private val collection = database.getCollection<TaskResultMongodb>("task_results") | ||||
|     override suspend fun save(taskResult: TaskResult): TaskResult = withContext(Dispatchers.IO) { | ||||
|         collection.replaceOne( | ||||
|             Filters.eq(taskResult.id.toString()), TaskResultMongodb.of(propertySerializerFactory, taskResult), | ||||
|             ReplaceOptions().upsert(true) | ||||
|         ) | ||||
|         return@withContext taskResult | ||||
|     } | ||||
| 
 | ||||
|     override fun findByTaskId(id: UUID): Flow<TaskResult> { | ||||
|         return collection.find(Filters.eq(id.toString())).map { it.toTaskResult(propertySerializerFactory) }.flowOn(Dispatchers.IO) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| data class TaskResultMongodb( | ||||
|     @BsonId | ||||
|     @BsonRepresentation(BsonType.STRING) | ||||
|     val id: String, | ||||
|     val taskId: String, | ||||
|     val success: Boolean, | ||||
|     val attempt: Int, | ||||
|     val result: Map<String, String>, | ||||
|     val message: String | ||||
| ) { | ||||
| 
 | ||||
|     fun toTaskResult(propertySerializerFactory: PropertySerializerFactory): TaskResult { | ||||
|         return TaskResult( | ||||
|             UUID.fromString(id), | ||||
|             UUID.fromString(taskId), | ||||
|             success, | ||||
|             attempt, | ||||
|             PropertySerializeUtils.deserialize(propertySerializerFactory, result), | ||||
|             message | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     companion object { | ||||
|         fun of(propertySerializerFactory: PropertySerializerFactory, taskResult: TaskResult): TaskResultMongodb { | ||||
|             return TaskResultMongodb( | ||||
|                 taskResult.id.toString(), | ||||
|                 taskResult.taskId.toString(), | ||||
|                 taskResult.success, | ||||
|                 taskResult.attempt, | ||||
|                 PropertySerializeUtils.serialize(propertySerializerFactory, taskResult.result), | ||||
|                 taskResult.message | ||||
|             ) | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -33,7 +33,8 @@ class OwlBrokerApplication( | |||
|     private val producerService: ProducerService, | ||||
|     private val subscribeTaskService: SubscribeTaskService, | ||||
|     private val taskPublishService: TaskPublishService, | ||||
|     private val taskManagementService: TaskManagementService | ||||
|     private val taskManagementService: TaskManagementService, | ||||
|     private val taskResultSubscribeService: TaskResultSubscribeService | ||||
| ) { | ||||
| 
 | ||||
|     private lateinit var server: Server | ||||
|  | @ -45,6 +46,7 @@ class OwlBrokerApplication( | |||
|             .addService(producerService) | ||||
|             .addService(subscribeTaskService) | ||||
|             .addService(taskPublishService) | ||||
|             .addService(taskResultSubscribeService) | ||||
|             .build() | ||||
| 
 | ||||
|         server.start() | ||||
|  |  | |||
|  | @ -30,4 +30,6 @@ interface TaskRepository { | |||
|     suspend fun findById(uuid: UUID): Task? | ||||
| 
 | ||||
|     suspend fun findByIdAndUpdate(id:UUID,task: Task) | ||||
| 
 | ||||
|     suspend fun findByPublishProducerIdAndCompletedAtIsNotNull(publishProducerId:UUID):Flow<Task> | ||||
| } | ||||
|  | @ -21,6 +21,7 @@ import java.util.* | |||
| 
 | ||||
| data class TaskResult( | ||||
|     val id: UUID, | ||||
|     val taskId:UUID, | ||||
|     val success: Boolean, | ||||
|     val attempt: Int, | ||||
|     val result: Map<String, PropertyValue<*>>, | ||||
|  |  | |||
|  | @ -16,6 +16,10 @@ | |||
| 
 | ||||
| package dev.usbharu.owl.broker.domain.model.taskresult | ||||
| 
 | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| import java.util.* | ||||
| 
 | ||||
| interface TaskResultRepository { | ||||
|     suspend fun save(taskResult: TaskResult):TaskResult | ||||
|     fun findByTaskId(id:UUID): Flow<TaskResult> | ||||
| } | ||||
|  | @ -27,6 +27,7 @@ import dev.usbharu.owl.common.property.PropertySerializerFactory | |||
| import kotlinx.coroutines.flow.Flow | ||||
| import kotlinx.coroutines.flow.collect | ||||
| import kotlinx.coroutines.flow.onEach | ||||
| import java.util.* | ||||
| import kotlin.coroutines.CoroutineContext | ||||
| import kotlin.coroutines.EmptyCoroutineContext | ||||
| 
 | ||||
|  | @ -40,7 +41,8 @@ class TaskResultService( | |||
|         requests.onEach { | ||||
|             taskManagementService.queueProcessed( | ||||
|                 TaskResult( | ||||
|                     id = it.id.toUUID(), | ||||
|                     id = UUID.randomUUID(), | ||||
|                     taskId = it.id.toUUID(), | ||||
|                     success = it.success, | ||||
|                     attempt = it.attempt, | ||||
|                     result = PropertySerializeUtils.deserialize(propertySerializerFactory, it.resultMap), | ||||
|  |  | |||
|  | @ -0,0 +1,62 @@ | |||
| /* | ||||
|  * Copyright (C) 2024 usbharu | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package dev.usbharu.owl.broker.interfaces.grpc | ||||
| 
 | ||||
| import TaskResultProducer.TaskResults | ||||
| import TaskResultSubscribeServiceGrpcKt | ||||
| import dev.usbharu.owl.Uuid | ||||
| import dev.usbharu.owl.broker.external.toUUID | ||||
| import dev.usbharu.owl.broker.service.TaskManagementService | ||||
| import dev.usbharu.owl.common.property.PropertySerializeUtils | ||||
| import dev.usbharu.owl.common.property.PropertySerializerFactory | ||||
| import dev.usbharu.owl.taskResult | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| import kotlinx.coroutines.flow.map | ||||
| import org.koin.core.annotation.Singleton | ||||
| import taskResults | ||||
| import kotlin.coroutines.CoroutineContext | ||||
| import kotlin.coroutines.EmptyCoroutineContext | ||||
| 
 | ||||
| @Singleton | ||||
| class TaskResultSubscribeService( | ||||
|     private val taskManagementService: TaskManagementService, | ||||
|     private val propertySerializerFactory: PropertySerializerFactory, | ||||
|     coroutineContext: CoroutineContext = EmptyCoroutineContext | ||||
| ) : | ||||
|     TaskResultSubscribeServiceGrpcKt.TaskResultSubscribeServiceCoroutineImplBase(coroutineContext) { | ||||
|     override fun subscribe(request: Uuid.UUID): Flow<TaskResults> { | ||||
|         return taskManagementService | ||||
|             .subscribeResult(request.toUUID()) | ||||
|             .map { | ||||
|                 taskResults { | ||||
|                     id = it.id.toUUID() | ||||
|                     name = it.name | ||||
|                     attempt = it.attempt | ||||
|                     success = it.success | ||||
|                     results.addAll(it.results.map { | ||||
|                         taskResult { | ||||
|                             id = it.taskId.toUUID() | ||||
|                             success = it.success | ||||
|                             attempt = it.attempt | ||||
|                             result.putAll(PropertySerializeUtils.serialize(propertySerializerFactory, it.result)) | ||||
|                             message = it.message | ||||
|                         } | ||||
|                     }) | ||||
|                 } | ||||
|             } | ||||
|     } | ||||
| } | ||||
|  | @ -23,14 +23,9 @@ import dev.usbharu.owl.broker.domain.model.task.Task | |||
| import dev.usbharu.owl.broker.domain.model.task.TaskRepository | ||||
| import dev.usbharu.owl.broker.domain.model.taskdefinition.TaskDefinitionRepository | ||||
| import dev.usbharu.owl.broker.domain.model.taskresult.TaskResult | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.coroutineScope | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| import kotlinx.coroutines.flow.collect | ||||
| import kotlinx.coroutines.flow.flowOf | ||||
| import kotlinx.coroutines.flow.onEach | ||||
| import kotlinx.coroutines.joinAll | ||||
| import kotlinx.coroutines.launch | ||||
| import dev.usbharu.owl.broker.domain.model.taskresult.TaskResultRepository | ||||
| import kotlinx.coroutines.* | ||||
| import kotlinx.coroutines.flow.* | ||||
| import org.koin.core.annotation.Singleton | ||||
| import org.slf4j.LoggerFactory | ||||
| import java.time.Instant | ||||
|  | @ -43,6 +38,8 @@ interface TaskManagementService { | |||
|     fun findAssignableTask(consumerId: UUID, numberOfConcurrent: Int): Flow<QueuedTask> | ||||
| 
 | ||||
|     suspend fun queueProcessed(taskResult: TaskResult) | ||||
| 
 | ||||
|     fun subscribeResult(producerId: UUID): Flow<TaskResults> | ||||
| } | ||||
| 
 | ||||
| @Singleton | ||||
|  | @ -53,7 +50,8 @@ class TaskManagementServiceImpl( | |||
|     private val assignQueuedTaskDecider: AssignQueuedTaskDecider, | ||||
|     private val retryPolicyFactory: RetryPolicyFactory, | ||||
|     private val taskRepository: TaskRepository, | ||||
|     private val queueScanner: QueueScanner | ||||
|     private val queueScanner: QueueScanner, | ||||
|     private val taskResultRepository: TaskResultRepository | ||||
| ) : TaskManagementService { | ||||
| 
 | ||||
|     private var taskFlow: Flow<Task> = flowOf() | ||||
|  | @ -132,8 +130,49 @@ class TaskManagementServiceImpl( | |||
|         val task = taskRepository.findById(taskResult.id) | ||||
|             ?: throw RecordNotFoundException("Task not found. id: ${taskResult.id}") | ||||
| 
 | ||||
|         taskRepository.findByIdAndUpdate(taskResult.id,task.copy(completedAt = Instant.now())) | ||||
| //todo タスク完了後の処理を書く | ||||
|         val taskDefinition = taskDefinitionRepository.findByName(task.name) | ||||
|             ?: throw TaskNotRegisterException("Task ${task.name} not definition.") | ||||
| 
 | ||||
|         val completedAt = if (taskResult.success) { | ||||
|             Instant.now() | ||||
|         } else if (taskResult.attempt >= taskDefinition.maxRetry) { | ||||
|             Instant.now() | ||||
|         } else { | ||||
|             null | ||||
|         } | ||||
| 
 | ||||
|         taskResultRepository.save(taskResult) | ||||
| 
 | ||||
|         taskRepository.findByIdAndUpdate( | ||||
|             taskResult.id, | ||||
|             task.copy(completedAt = completedAt, attempt = taskResult.attempt) | ||||
|         ) | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     override fun subscribeResult(producerId: UUID): Flow<TaskResults> { | ||||
|         return flow { | ||||
| 
 | ||||
|             while (currentCoroutineContext().isActive) { | ||||
|                 taskRepository | ||||
|                     .findByPublishProducerIdAndCompletedAtIsNotNull(producerId) | ||||
|                     .onEach { | ||||
|                         val results = taskResultRepository.findByTaskId(it.id).toList() | ||||
|                         emit( | ||||
|                             TaskResults( | ||||
|                                 it.name, | ||||
|                                 it.id, | ||||
|                                 results.any { it.success }, | ||||
|                                 it.attempt, | ||||
|                                 results | ||||
|                             ) | ||||
|                         ) | ||||
|                     } | ||||
|                 delay(500) | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     companion object { | ||||
|  |  | |||
|  | @ -0,0 +1,28 @@ | |||
| /* | ||||
|  * Copyright (C) 2024 usbharu | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package dev.usbharu.owl.broker.service | ||||
| 
 | ||||
| import dev.usbharu.owl.broker.domain.model.taskresult.TaskResult | ||||
| import java.util.* | ||||
| 
 | ||||
| data class TaskResults( | ||||
|     val name:String, | ||||
|     val id:UUID, | ||||
|     val success:Boolean, | ||||
|     val attempt:Int, | ||||
|     val results: List<TaskResult> | ||||
| ) | ||||
|  | @ -0,0 +1,15 @@ | |||
| syntax = "proto3"; | ||||
| import "uuid.proto"; | ||||
| import "task_result.proto"; | ||||
| 
 | ||||
| message TaskResults { | ||||
|   string name = 1; | ||||
|   UUID id = 2; | ||||
|   bool success = 3; | ||||
|   int32 attempt = 4; | ||||
|   repeated TaskResult results = 5; | ||||
| } | ||||
| 
 | ||||
| service TaskResultSubscribeService { | ||||
|   rpc subscribe(UUID) returns (stream TaskResults); | ||||
| } | ||||
		Loading…
	
		Reference in New Issue