DomainTemplateService.java

package fr.tiogars.domaintemplate.domains.domaintemplate.services;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import fr.tiogars.domaintemplate.domains.domaintemplate.entities.DomainTemplate;
import fr.tiogars.domaintemplate.domains.domaintemplate.entities.DomainTemplateEntity;
import fr.tiogars.domaintemplate.domains.domaintemplate.models.CreateDomainTemplateRequest;
import fr.tiogars.domaintemplate.domains.domaintemplate.models.UpdateDomainTemplateRequest;
import fr.tiogars.domaintemplate.domains.domaintemplate.repositories.DomainTemplateRepository;

import java.time.Clock;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * Coordinates persistence and retrieval of DomainTemplates.
 *
 * @since 0.1.0
 */
@Service
public class DomainTemplateService {

    private final Clock clock;
    private final DomainTemplateRepository repository;

    /**
     * Creates a service using the system UTC clock.
     *
     * @param repository domain record repository
     */
    @Autowired
    public DomainTemplateService(DomainTemplateRepository repository) {
        this(repository, Clock.systemUTC());
    }

    /**
     * Creates a service using a supplied clock.
     *
     * @param repository domain record repository
     * @param clock clock used to timestamp stored records
     */
    DomainTemplateService(DomainTemplateRepository repository, Clock clock) {
        this.repository = repository;
        this.clock = clock;
    }

    /**
     * Creates a DomainTemplate when its code does not already exist.
     *
     * @param request business data to create
     * @return created template, or an empty optional when the code exists
     */
    @Transactional
    public Optional<DomainTemplate> create(CreateDomainTemplateRequest request) {
        if (repository.findByCode(request.code()).isPresent()) {
            return Optional.empty();
        }
        Instant timestamp = currentTimestamp();
        DomainTemplateEntity entity = new DomainTemplateEntity(
                request.code(), request.label(), request.description(), timestamp, timestamp);
        return Optional.of(toRecord(repository.save(entity)));
    }

    /**
     * Updates the mutable fields of a DomainTemplate.
     *
     * @param id persistent identifier
     * @param request replacement business data
     * @return updated template, or an empty optional when it does not exist
     */
    @Transactional
    public Optional<DomainTemplate> update(Long id, UpdateDomainTemplateRequest request) {
        return repository.findById(id)
                .map(existing -> {
                    existing.update(request.label(), request.description(), currentTimestamp());
                    return toRecord(repository.save(existing));
                });
    }

    /**
     * Deletes a DomainTemplate.
     *
     * @param id persistent identifier
     * @return {@code true} when the template existed
     */
    @Transactional
    public boolean delete(Long id) {
        if (!repository.existsById(id)) {
            return false;
        }
        repository.deleteById(id);
        return true;
    }

    /**
     * Lists all templates ordered by business code.
     *
     * @return templates ordered by code
     */
    public List<DomainTemplate> findAll() {
        return repository.findAllByOrderByCodeAsc().stream()
                .map(DomainTemplateService::toRecord)
                .toList();
    }

    /**
     * Finds a DomainTemplate by identifier.
     *
     * @param id persistent identifier
     * @return matching template, or an empty optional when none exists
     */
    public Optional<DomainTemplate> findById(Long id) {
        return repository.findById(id)
                .map(DomainTemplateService::toRecord);
    }

    /**
     * Creates or updates templates by immutable business code.
     *
     * @param requests business data to seed
     * @return stored templates in input order
     */
    @Transactional
    public List<DomainTemplate> seed(List<CreateDomainTemplateRequest> requests) {
        List<DomainTemplate> storedTemplates = new ArrayList<>();
        for (CreateDomainTemplateRequest request : requests) {
            Instant timestamp = currentTimestamp();
            DomainTemplateEntity entity = repository.findByCode(request.code())
                    .map(existing -> {
                        existing.update(request.label(), request.description(), timestamp);
                        return existing;
                    })
                    .orElseGet(() -> new DomainTemplateEntity(
                            request.code(), request.label(), request.description(), timestamp, timestamp));
            storedTemplates.add(toRecord(repository.save(entity)));
        }
        return storedTemplates;
    }

    private Instant currentTimestamp() {
        return Instant.now(clock).truncatedTo(ChronoUnit.MICROS);
    }

    private static DomainTemplate toRecord(DomainTemplateEntity entity) {
        return new DomainTemplate(
                entity.getId(),
                entity.getCode(),
                entity.getLabel(),
                entity.getDescription(),
                entity.getCreatedDate(),
                entity.getLastModifiedDate());
    }
}