Skip to content

Feature Implementation Plan: Complete API Caching Implementation

📋 Todo Checklist

  • [ ] Prerequisite: Verify that the DTO/Mapper/Caching pattern is working for /api/varieties and /api/locations/regions.
  • [ ] List Endpoints:
    • [ ] Apply the DTO caching pattern to the /api/processing-methods endpoint.
    • [ ] Apply the DTO caching pattern to the /api/coffee-beans endpoint.
    • [ ] Apply the DTO caching pattern to the /api/roasters endpoint.
    • [ ] Apply the DTO caching pattern to the /api/species endpoint.
    • [ ] Apply the DTO caching pattern to the /api/roast-levels endpoint.
    • [ ] Apply the DTO caching pattern to the /api/locations/countries endpoint.
  • [ ] Detail Endpoints:
    • [ ] Implement caching for all individual detail (/{id}) endpoints.
  • [ ] Static Endpoints:
    • [ ] Implement caching for /api/filters/metadata and /api/flavor-wheel.
  • [ ] Invalidation:
    • [ ] Update the CacheInvalidationSubscriber to handle all newly cached entity types.
  • [ ] Testing:
    • [ ] Write integration tests to verify caching for all newly covered endpoints.

🔍 Analysis & Investigation

Codebase Structure

  • DTOs: New DTO classes will be created in src/DTO/Api/ for CoffeeBean, Roaster, RoastLevel, etc.
  • Mapper: The src/Service/Api/Mapper/EntityToDtoMapper.php will be expanded with new methods to handle the conversion for all new DTOs.
  • Repositories: The findByRequest and find methods in all relevant repositories will be updated to implement the caching pattern.
  • Event Subscriber: The src/EventSubscriber/CacheInvalidationSubscriber.php will be updated to be comprehensive.

Current Architecture & Problem

  • Problem: The high-performance, DTO-based caching architecture has been successfully implemented for Varieties and Regions but has not yet been rolled out across the entire API. This leaves critical endpoints like /api/coffee-beans uncached and vulnerable to performance issues.
  • Solution: This plan is the final step in the caching initiative. It applies the now-proven DTO caching pattern systematically to every remaining read-only endpoint, ensuring 100% cache coverage and a consistently fast API.

Dependencies & Integration Points

  • This plan builds directly on the successful implementation for Varieties and Regions. It uses the same established patterns and services (EntityToDtoMapper, CacheKeyGenerator, TagAwareCacheInterface).

Considerations & Challenges

  • CoffeeBean Complexity: The CoffeeBean entity is the most complex, with many relationships. Creating its DTO (CoffeeBeanDTO) and mapping logic will require careful attention to ensure all necessary nested data (like VarietyDTO, RegionDTO, etc.) is included correctly.
  • Thoroughness: The main challenge is meticulous execution. Every endpoint must be updated, and every entity must have a corresponding entry in the CacheInvalidationSubscriber.

📝 Implementation Plan

Prerequisites

  • The DTO, Mapper, and caching pattern is already successfully implemented for Varieties and Regions.

Step-by-Step Implementation

  1. Expand DTOs and Mapper

    • Action: For each of the following entities, create a corresponding DTO in src/DTO/Api/ and a mapping method in EntityToDtoMapper:
      • ProcessingMethod -> ProcessingMethodDTO
      • CoffeeBean -> CoffeeBeanDTO (This will be the most complex, including nested DTOs for its relations).
      • Roaster -> RoasterDTO
      • Species -> SpeciesDTO
      • RoastLevel -> RoastLevelDTO
      • Country -> CountryDTO
  2. Refactor List Endpoints

    • Action: For each of the corresponding repositories (ProcessingMethodRepository, CoffeeBeanRepository, etc.), refactor the findByRequest method to:
      1. Use the CacheKeyGenerator.
      2. Wrap the logic in an apiCache->get() call.
      3. Tag the cache item appropriately (e.g., ['coffee_bean_list']).
      4. Fetch the paginated entities.
      5. Use the EntityToDtoMapper to convert the entities to DTOs.
      6. Return the cacheable array ['items' => $dtos, 'totalItems' => $count].
    • Action: Update the corresponding controller methods to handle the new array-based return type from the repository.
  3. Implement Caching for Detail (/{id}) Endpoints

    • Action: For every repository, update the find() method (or the method used by the detail endpoint) to cache its result.
    • Example (RoasterRepository):
      public function find($id, ...): ?Roaster
      {
          $cacheKey = 'roaster_detail_' . $id;
          return $this->apiCache->get($cacheKey, function (ItemInterface $item) use ($id) {
              $item->tag(['roasters_detail', 'roaster_' . $id]);
              // The actual find logic
              return $this->createQueryBuilder('r')->where('r.id = :id')->setParameter('id', $id)->getQuery()->getOneOrNullResult();
          });
      }
      
  4. Update CacheInvalidationSubscriber

    • Files to modify: src/EventSubscriber/CacheInvalidationSubscriber.php
    • Action: This is a critical step. Add a match arm for every entity that is now cached.
      // Inside the invalidate() method's match expression
      $entity instanceof CoffeeBean => ['coffee_bean_list', 'top_varieties', 'top_regions', 'coffee_bean_detail'],
      $entity instanceof Roaster    => ['roasters_list', 'roasters_detail'],
      // ... ADD A CASE FOR EVERY OTHER CACHED ENTITY ...
      

Testing Strategy

  • Integration Tests:
    • For each newly cached endpoint (both list and detail), create a test that follows the "request -> update entity -> request again" pattern to verify that cache invalidation is working correctly.
    • Verify that the JSON response for each endpoint correctly reflects the structure of its DTO.

🎯 Success Criteria

  • All read-only API endpoints, including the critical /api/coffee-beans, are now cached using the DTO pattern.
  • The CacheInvalidationSubscriber is comprehensive, ensuring data freshness across the entire API.
  • The API is consistently fast and resilient, with cache coverage at or near 100% for read operations.
  • The refactor-lists-for-caching-with-dtos.md plan is now fully superseded and can be removed.