Skip to content

9.4. サービス実装パターン

9.4.1. StorageService インターフェース

java
public interface StorageService {

  FileResponse upload(MultipartFile file, Long uploadedBy);

  FileDownload download(Long fileId);

  void delete(Long fileId);
}

9.4.2. 実装クラスの構造

java
@Service
@RequiredArgsConstructor
public class LocalStorageService implements StorageService {

  private final StorageProperties properties;
  private final FileMetadataRepository fileMetadataRepository;
  private final FileValidator fileValidator;

  @Override
  @Transactional
  public FileResponse upload(MultipartFile file, Long uploadedBy) {
    fileValidator.validate(file);

    String storedFilename = FileNameGenerator.generate(file.getOriginalFilename());
    Path targetPath = StoragePath.resolve(properties.uploadPath(), storedFilename);

    try {
      Files.createDirectories(targetPath.getParent());
      Files.copy(file.getInputStream(), targetPath);
    } catch (IOException e) {
      throw new StorageException("ファイルの保存に失敗しました。", e);
    }

    FileMetadataRecord record = fileMetadataRepository.insert(
        file.getOriginalFilename(),
        storedFilename,
        targetPath.toString(),
        file.getContentType(),
        file.getSize(),
        uploadedBy
    );

    return FileResponse.from(record);
  }

  @Override
  @Transactional(readOnly = true)
  public FileDownload download(Long fileId) {
    FileMetadataRecord metadata = fileMetadataRepository.findById(fileId)
        .orElseThrow(() -> new FileNotFoundException(fileId));

    Path filePath = Path.of(metadata.getFilePath());
    Resource resource = new FileSystemResource(filePath);

    if (!resource.exists()) {
      throw new FileNotFoundException(fileId);
    }

    return new FileDownload(
        resource,
        metadata.getOriginalFilename(),
        metadata.getContentType()
    );
  }

  @Override
  @Transactional
  public void delete(Long fileId) {
    FileMetadataRecord metadata = fileMetadataRepository.findById(fileId)
        .orElseThrow(() -> new FileNotFoundException(fileId));

    try {
      Files.deleteIfExists(Path.of(metadata.getFilePath()));
    } catch (IOException e) {
      log.error("ファイル削除失敗: fileId={}", fileId, e);
    }

    fileMetadataRepository.deleteById(fileId);
  }
}

拡張

現在はローカルファイルシステムを使用していますが、将来クラウドストレージ(S3など)へ移行する場合、StorageService インターフェースの新しい実装クラスを追加するだけで対応できます。インターフェースベースの設計により、ストレージ戦略を柔軟に変更することができます。

TIENIPIA QUALIFIED STANDARD