From a6850d3d65135295f3d599314e617c93928390c4 Mon Sep 17 00:00:00 2001 From: Dylan De Faoite Date: Sat, 20 Jun 2026 18:50:14 +0100 Subject: [PATCH] refactor(jobs): split JobService into JobRegistryService and JobOrchestrationService --- .../controllers/MetadataController.java | 14 ++-- .../vodsystem/services/DownloadService.java | 10 +-- .../ddf/vodsystem/services/EditService.java | 23 +++-- ...vice.java => JobOrchestrationService.java} | 84 ++++++++++--------- .../services/JobRegistryService.java | 47 +++++++++++ .../ddf/vodsystem/services/UploadService.java | 8 +- 6 files changed, 123 insertions(+), 63 deletions(-) rename src/main/java/com/ddf/vodsystem/services/{JobService.java => JobOrchestrationService.java} (59%) create mode 100644 src/main/java/com/ddf/vodsystem/services/JobRegistryService.java diff --git a/src/main/java/com/ddf/vodsystem/controllers/MetadataController.java b/src/main/java/com/ddf/vodsystem/controllers/MetadataController.java index 53d569b..0eb58e8 100644 --- a/src/main/java/com/ddf/vodsystem/controllers/MetadataController.java +++ b/src/main/java/com/ddf/vodsystem/controllers/MetadataController.java @@ -2,7 +2,8 @@ package com.ddf.vodsystem.controllers; import com.ddf.vodsystem.dto.ClipOptions; import com.ddf.vodsystem.dto.APIResponse; -import com.ddf.vodsystem.services.JobService; +import com.ddf.vodsystem.services.JobOrchestrationService; +import com.ddf.vodsystem.services.JobRegistryService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -13,15 +14,16 @@ import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/v1/metadata") public class MetadataController { - private final JobService jobService; + private final JobRegistryService jobRegistryService; - public MetadataController(JobService jobService) { - this.jobService = jobService; + public MetadataController( + JobRegistryService jobRegistryService) { + this.jobRegistryService = jobRegistryService; } @GetMapping("/original/{uuid}") public ResponseEntity> getMetadata(@PathVariable String uuid) { - ClipOptions originalMetadata = jobService.getJob(uuid).getInputClipOptions(); + ClipOptions originalMetadata = jobRegistryService.getJob(uuid).getInputClipOptions(); if (originalMetadata == null) { return ResponseEntity.status(HttpStatus.NOT_FOUND) @@ -34,7 +36,7 @@ public class MetadataController { @GetMapping("/converted/{uuid}") public ResponseEntity> getConvertedMetadata(@PathVariable String uuid) { - ClipOptions convertedMetadata = jobService.getJob(uuid).getOutputClipOptions(); + ClipOptions convertedMetadata = jobRegistryService.getJob(uuid).getOutputClipOptions(); if (convertedMetadata == null) { return ResponseEntity.status(HttpStatus.NOT_FOUND) diff --git a/src/main/java/com/ddf/vodsystem/services/DownloadService.java b/src/main/java/com/ddf/vodsystem/services/DownloadService.java index 022ed96..6373a32 100644 --- a/src/main/java/com/ddf/vodsystem/services/DownloadService.java +++ b/src/main/java/com/ddf/vodsystem/services/DownloadService.java @@ -15,27 +15,27 @@ import java.io.File; @Service public class DownloadService { - private final JobService jobService; + private final JobRegistryService jobRegistryService; private final ClipRepository clipRepository; private final ClipService clipService; - public DownloadService(JobService jobService, + public DownloadService(JobRegistryService jobRegistryService, ClipRepository clipRepository, ClipService clipService) { - this.jobService = jobService; + this.jobRegistryService = jobRegistryService; this.clipRepository = clipRepository; this.clipService = clipService; } public Resource downloadInput(String uuid) { - Job job = jobService.getJob(uuid); + Job job = jobRegistryService.getJob(uuid); File file = job.getInputFile(); return new FileSystemResource(file); } public Resource downloadOutput(String uuid) { - Job job = jobService.getJob(uuid); + Job job = jobRegistryService.getJob(uuid); if (!job.getStatus().getProcess().isComplete()) { throw new JobNotFinished("Job is not finished"); diff --git a/src/main/java/com/ddf/vodsystem/services/EditService.java b/src/main/java/com/ddf/vodsystem/services/EditService.java index b22d8c8..62d7559 100644 --- a/src/main/java/com/ddf/vodsystem/services/EditService.java +++ b/src/main/java/com/ddf/vodsystem/services/EditService.java @@ -8,17 +8,22 @@ import org.springframework.stereotype.Service; @Service public class EditService { - private final JobService jobService; private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(EditService.class); private final MetadataService metadataService; + private final JobRegistryService jobRegistryService; + private final JobOrchestrationService jobOrchestrationService; - public EditService(JobService jobService, MetadataService metadataService) { - this.jobService = jobService; + public EditService(JobOrchestrationService jobOrchestrationService, + MetadataService metadataService, + JobRegistryService jobRegistryService) { this.metadataService = metadataService; + this.jobRegistryService = jobRegistryService; + this.jobOrchestrationService = jobOrchestrationService; + } public void edit(String uuid, ClipOptions clipOptions) { - Job job = jobService.getJob(uuid); + Job job = jobRegistryService.getJob(uuid); metadataService.validateMetadata(job.getInputClipOptions(), clipOptions); job.setOutputClipOptions(clipOptions); @@ -26,21 +31,21 @@ public class EditService { } public void process(String uuid) { - Job job = jobService.getJob(uuid); - jobService.processJob(job); + Job job = jobRegistryService.getJob(uuid); + jobOrchestrationService.processJob(job); logger.info("Job {} - Started processing", uuid); } public void convert(String uuid) { - Job job = jobService.getJob(uuid); - jobService.convertJob(job); + Job job = jobRegistryService.getJob(uuid); + jobOrchestrationService.convertJob(job); logger.info("Job {} - Started converting", uuid); } public JobStatus getStatus(String uuid) { - Job job = jobService.getJob(uuid); + Job job = jobRegistryService.getJob(uuid); return job.getStatus(); } } diff --git a/src/main/java/com/ddf/vodsystem/services/JobService.java b/src/main/java/com/ddf/vodsystem/services/JobOrchestrationService.java similarity index 59% rename from src/main/java/com/ddf/vodsystem/services/JobService.java rename to src/main/java/com/ddf/vodsystem/services/JobOrchestrationService.java index 311649c..85bc83a 100644 --- a/src/main/java/com/ddf/vodsystem/services/JobService.java +++ b/src/main/java/com/ddf/vodsystem/services/JobOrchestrationService.java @@ -17,16 +17,20 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; -import com.ddf.vodsystem.exceptions.JobNotFound; - /** - * Service for managing and processing jobs in a background thread. - * Uses a blocking queue to avoid busy waiting and ensures jobs are processed sequentially. + * Orchestrates the processing pipeline for a {@link Job}: converting (remuxing) + * the input file and, separately, compressing/persisting the output clip. + *

+ * Job storage and lookup are handled by {@link JobRegistryService}; this class + * is only responsible for acting on a {@link Job} instance once it's been retrieved. + * Long-running steps (remux, compress) are kicked off asynchronously via + * {@link java.util.concurrent.CompletableFuture}-returning service calls rather + * than blocked on, so callers should not assume the job is finished when these + * methods return. */ @Service -public class JobService { - private static final Logger logger = LoggerFactory.getLogger(JobService.class); - private final ConcurrentHashMap jobs = new ConcurrentHashMap<>(); +public class JobOrchestrationService { + private static final Logger logger = LoggerFactory.getLogger(JobOrchestrationService.class); private final ClipService clipService; private final RemuxService remuxService; private final DirectoryService directoryService; @@ -34,14 +38,19 @@ public class JobService { private final UserService userService; /** - * Constructs a JobService with the given CompressionService. - * @param clipService the compression service to use for processing jobs + * Constructs the orchestration service with its collaborating services. + * + * @param clipService used to persist a completed clip's metadata once processing succeeds + * @param remuxService performs the remux step during conversion + * @param compressionService performs the compression step during processing + * @param directoryService used for filesystem operations (e.g. copying the input file to a temp location) + * @param userService used to resolve the currently logged-in user when persisting a clip */ - public JobService(ClipService clipService, - RemuxService remuxService, - CompressionService compressionService, - DirectoryService directoryService, - UserService userService) { + public JobOrchestrationService(ClipService clipService, + RemuxService remuxService, + CompressionService compressionService, + DirectoryService directoryService, + UserService userService) { this.clipService = clipService; this.remuxService = remuxService; this.directoryService = directoryService; @@ -50,30 +59,19 @@ public class JobService { } /** - * Adds a new job to the job map. - * @param job the job to add + * Converts a job's input file in place by remuxing it. + *

+ * The original input file is first copied to a temporary {@code .temp} file so the + * remux can read from a stable source; on successful completion the temp file is + * deleted. The remux itself runs asynchronously — this method returns once the + * remux has been kicked off, not once it has finished. Progress/completion is + * tracked via {@code job.getStatus().getConversion()}, which is reset at the + * start of this call and marked complete in the remux's completion callback. + * + * @param job the job whose input file should be converted + * @throws StorageException if the temporary file cannot be created, or if it + * cannot be deleted after a successful remux */ - public void add(Job job) { - logger.info("Added job: {}", job.getUuid()); - jobs.put(job.getUuid(), job); - } - - /** - * Retrieves a job by its UUID. - * @param uuid the UUID of the job - * @return the Job object - * @throws JobNotFound if the job does not exist - */ - public Job getJob(String uuid) { - Job job = jobs.get(uuid); - - if (job == null) { - throw new JobNotFound("Job not found"); - } - - return job; - } - public void convertJob(Job job) { logger.info("Converting job: {}", job.getUuid()); File tempFile = new File(job.getInputFile().getAbsolutePath() + ".temp"); @@ -115,8 +113,16 @@ public class JobService { } /** - * Marks a job as ready and adds it to the processing queue. - * @param job The job to process + * Compresses a job's output file according to its output clip options and, on + * success, persists the resulting clip against the currently logged-in user (if any). + *

+ * Compression runs asynchronously — this method returns once compression has been + * kicked off, not once it has finished. Progress is tracked via + * {@code job.getStatus().getProcess()}, which is reset at the start of this call. + * If compression or persistence fails, the job's status is marked failed with the + * triggering exception's message. + * + * @param job the job to process */ public void processJob(Job job) { logger.info("Job ready: {}", job.getUuid()); diff --git a/src/main/java/com/ddf/vodsystem/services/JobRegistryService.java b/src/main/java/com/ddf/vodsystem/services/JobRegistryService.java new file mode 100644 index 0000000..266ea7e --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/services/JobRegistryService.java @@ -0,0 +1,47 @@ +package com.ddf.vodsystem.services; + +import com.ddf.vodsystem.dto.Job; +import com.ddf.vodsystem.exceptions.JobNotFound; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * In-memory store for {@link Job} instances, keyed by job UUID. + * Provides add/lookup only — actual job processing is handled by + * {@link JobOrchestrationService}. + */ +@Service +public class JobRegistryService { + private static final Logger logger = LoggerFactory.getLogger(JobRegistryService.class); + private final ConcurrentHashMap jobs = new ConcurrentHashMap<>(); + + /** + * Adds a new job to the job map. + * + * @param job the job to add + */ + public void add(Job job) { + logger.info("Added job: {}", job.getUuid()); + jobs.put(job.getUuid(), job); + } + + /** + * Retrieves a job by its UUID. + * + * @param uuid the UUID of the job + * @return the Job object + * @throws JobNotFound if the job does not exist + */ + public Job getJob(String uuid) { + Job job = jobs.get(uuid); + + if (job == null) { + throw new JobNotFound("Job not found"); + } + + return job; + } +} \ No newline at end of file diff --git a/src/main/java/com/ddf/vodsystem/services/UploadService.java b/src/main/java/com/ddf/vodsystem/services/UploadService.java index 77c3044..c25bc7a 100644 --- a/src/main/java/com/ddf/vodsystem/services/UploadService.java +++ b/src/main/java/com/ddf/vodsystem/services/UploadService.java @@ -24,14 +24,14 @@ import org.slf4j.LoggerFactory; public class UploadService { private static final Logger logger = LoggerFactory.getLogger(UploadService.class); - private final JobService jobService; + private final JobRegistryService jobRegistryService; private final MetadataService metadataService; private final DirectoryService directoryService; - public UploadService(JobService jobService, + public UploadService(JobRegistryService jobRegistryService, MetadataService metadataService, DirectoryService directoryService) { - this.jobService = jobService; + this.jobRegistryService = jobRegistryService; this.metadataService = metadataService; this.directoryService = directoryService; } @@ -54,7 +54,7 @@ public class UploadService { ClipOptions clipOptions = getMetadataWithTimeout(inputFile); Job job = new Job(uuid, inputFile, outputFile, clipOptions); - jobService.add(job); + jobRegistryService.add(job); return uuid; }