refactor(jobs): split JobService into JobRegistryService and JobOrchestrationService

This commit is contained in:
2026-06-20 18:50:14 +01:00
parent 7cee64beb3
commit a6850d3d65
6 changed files with 123 additions and 63 deletions

View File

@@ -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<APIResponse<ClipOptions>> 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<APIResponse<ClipOptions>> 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)

View File

@@ -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");

View File

@@ -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();
}
}

View File

@@ -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.
* <p>
* 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<String, Job> 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.
* <p>
* 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).
* <p>
* 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());

View File

@@ -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<String, Job> 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;
}
}

View File

@@ -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;
}