refactor: extract ModificationManager for modification creation

Move create/batchCreate transactional logic from ModificationService into
a dedicated ModificationManager. Both ModificationService and WebhookService
delegate to the manager, respecting the Controller -> Service -> Manager
layering rule.
This commit is contained in:
2026-06-01 15:30:14 +08:00
parent 8c8ca58b74
commit 7fafa0d903
2 changed files with 121 additions and 41 deletions
@@ -0,0 +1,115 @@
package com.onixbyte.deltaforceguide.manager;
import com.onixbyte.deltaforceguide.domain.entity.Accessory;
import com.onixbyte.deltaforceguide.domain.entity.Firearm;
import com.onixbyte.deltaforceguide.domain.entity.Modification;
import com.onixbyte.deltaforceguide.domain.entity.Tuning;
import com.onixbyte.deltaforceguide.domain.dto.AccessoryRequest;
import com.onixbyte.deltaforceguide.domain.dto.ModificationRequest;
import com.onixbyte.deltaforceguide.domain.dto.ModificationResponse;
import com.onixbyte.deltaforceguide.domain.dto.TuningRequest;
import com.onixbyte.deltaforceguide.repository.FirearmRepository;
import com.onixbyte.deltaforceguide.repository.ModificationRepository;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@Component
public class ModificationManager {
private final ModificationRepository modificationRepository;
private final FirearmRepository firearmRepository;
public ModificationManager(
ModificationRepository modificationRepository,
FirearmRepository firearmRepository
) {
this.modificationRepository = modificationRepository;
this.firearmRepository = firearmRepository;
}
@Transactional
public ModificationResponse create(ModificationRequest request) {
var firearm = firearmRepository.findById(request.firearmId())
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND,
"Firearm not found: " + request.firearmId()));
var modification = toEntity(request, firearm);
return ModificationResponse.from(modificationRepository.save(modification));
}
@Transactional
public List<ModificationResponse> batchCreate(List<ModificationRequest> requests) {
var firearmIds = requests.stream()
.map(ModificationRequest::firearmId)
.collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new));
Map<Long, Firearm> firearmMap = new HashMap<>();
firearmRepository.findAllById(firearmIds)
.forEach(firearm -> firearmMap.put(firearm.getId(), firearm));
if (firearmMap.size() != firearmIds.size()) {
var missing = firearmIds.stream()
.filter((id) -> !firearmMap.containsKey(id))
.toList();
throw new ResponseStatusException(HttpStatus.NOT_FOUND,
"Firearm not found: " + missing);
}
var modifications = requests.stream()
.map(req -> toEntity(req, firearmMap.get(req.firearmId())))
.toList();
return modificationRepository.saveAll(modifications)
.stream()
.map(ModificationResponse::from)
.toList();
}
private Modification toEntity(ModificationRequest request, Firearm firearm) {
return Modification.builder()
.firearm(firearm)
.name(request.name())
.code(request.code())
.tags(request.tags())
.accessories(toAccessories(request.accessories()))
.note(request.note())
.author(request.author())
.videoUrl(request.videoUrl())
.build();
}
private List<Accessory> toAccessories(List<AccessoryRequest> requests) {
if (requests == null) {
return new ArrayList<>();
}
return requests.stream().map(this::toAccessory).toList();
}
private Accessory toAccessory(AccessoryRequest request) {
var accessory = new Accessory();
accessory.setSlotName(request.slotName());
accessory.setAccessoryName(request.accessoryName());
accessory.setTunings(toTunings(request.tunings()));
return accessory;
}
private List<Tuning> toTunings(List<TuningRequest> requests) {
if (requests == null) {
return new ArrayList<>();
}
return requests.stream().map(this::toTuning).toList();
}
private Tuning toTuning(TuningRequest request) {
var tuning = new Tuning();
tuning.setTuningName(request.tuningName());
tuning.setTuningValue(request.tuningValue());
return tuning;
}
}
@@ -9,6 +9,7 @@ import com.onixbyte.deltaforceguide.domain.entity.Accessory;
import com.onixbyte.deltaforceguide.domain.entity.Firearm;
import com.onixbyte.deltaforceguide.domain.entity.Modification;
import com.onixbyte.deltaforceguide.domain.entity.Tuning;
import com.onixbyte.deltaforceguide.manager.ModificationManager;
import com.onixbyte.deltaforceguide.repository.FirearmRepository;
import com.onixbyte.deltaforceguide.repository.ModificationRepository;
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -21,10 +22,8 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Service
@@ -32,15 +31,18 @@ public class ModificationService {
private final ModificationRepository modificationRepository;
private final FirearmRepository firearmRepository;
private final ModificationManager modificationManager;
private final ObjectMapper objectMapper;
public ModificationService(
ModificationRepository modificationRepository,
FirearmRepository firearmRepository,
ModificationManager modificationManager,
ObjectMapper objectMapper
) {
this.modificationRepository = modificationRepository;
this.firearmRepository = firearmRepository;
this.modificationManager = modificationManager;
this.objectMapper = objectMapper;
}
@@ -79,36 +81,12 @@ public class ModificationService {
@Transactional
public ModificationResponse create(ModificationRequest request) {
Firearm firearm = firearmRepository.findById(request.firearmId())
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Firearm not found: " + request.firearmId()));
Modification modification = toEntity(request, firearm);
return ModificationResponse.from(modificationRepository.save(modification));
return modificationManager.create(request);
}
@Transactional
public List<ModificationResponse> batchCreate(List<ModificationRequest> requests) {
Set<Long> firearmIds = requests.stream()
.map(ModificationRequest::firearmId)
.collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new));
Map<Long, Firearm> firearmMap = new HashMap<>();
firearmRepository.findAllById(firearmIds).forEach(firearm -> firearmMap.put(firearm.getId(), firearm));
if (firearmMap.size() != firearmIds.size()) {
List<Long> missingFirearmIds = firearmIds.stream()
.filter(id -> !firearmMap.containsKey(id))
.toList();
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Firearm not found: " + missingFirearmIds);
}
List<Modification> modifications = requests.stream()
.map(request -> toEntity(request, firearmMap.get(request.firearmId())))
.toList();
return modificationRepository.saveAll(modifications)
.stream()
.map(ModificationResponse::from)
.toList();
return modificationManager.batchCreate(requests);
}
@Transactional
@@ -155,19 +133,6 @@ public class ModificationService {
modificationRepository.deleteAllInBatch(modifications);
}
private Modification toEntity(ModificationRequest request, Firearm firearm) {
return Modification.builder()
.firearm(firearm)
.name(request.name())
.code(request.code())
.tags(safeTags(request.tags()))
.accessories(toAccessories(request.accessories()))
.note(request.note())
.author(request.author())
.videoUrl(request.videoUrl())
.build();
}
private List<String> safeTags(List<String> tags) {
return tags == null ? new ArrayList<>() : tags;
}