Skip to content

Feature Implementation Plan: Roaster Shipping API

📋 Todo Checklist

  • [ ] Create RoasterShippingRule entity and migration
  • [ ] Update Roaster and ShippingRegion entities
  • [ ] Implement ShipsTo DTOs
  • [ ] Update EntityToDtoMapper to process shipping rules
  • [ ] Update RoasterDTO to include shipsTo data
  • [ ] Update RoasterRepository to fetch new relationships
  • [ ] Final Review and Testing

🔍 Analysis & Investigation

Codebase Structure

  • Entities: Located in src/Entity/. Key entities are Roaster, ShippingRegion, and Country. A new entity, RoasterShippingRule, will be created.
  • Controllers: API controllers are in src/Controller/Api/. RoasterController.php is the relevant controller.
  • Repositories: Located in src/Repository/. RoasterRepository.php fetches roaster data.
  • DTOs: API data transfer objects are in src/DTO/Api/. RoasterDTO.php will be modified.
  • Mappers: The EntityToDtoMapper service in src/Service/Api/Mapper/ is responsible for converting entities to DTOs.

Current Architecture

The API uses a standard Symfony setup with controllers, repositories, and entities. For the API layer, it uses DTOs to decouple the API schema from the database schema. An EntityToDtoMapper service handles the transformation from Doctrine entities to DTOs. Caching is used at the repository level for performance.

Dependencies & Integration Points

  • Doctrine: For database interactions and schema management.
  • Symfony Serializer: For converting DTOs to JSON.
  • NelmioApiDocBundle: For API documentation.

Considerations & Challenges

  • Missing Relationship: The core challenge is that there is no existing relationship between Roaster and ShippingRegion. This plan addresses this by creating a new join entity, RoasterShippingRule.
  • Exclusion Logic: The requirement to handle country exclusions within a shipping region adds complexity. The RoasterShippingRule entity is designed to handle this by having a ManyToMany relationship with excluded countries.
  • API Caching: The existing API has caching at the repository layer. Any changes to the data fetching must be compatible with the caching strategy. The cache for roasters will need to be invalidated after the changes are deployed.

📝 Implementation Plan

Prerequisites

  • Ensure you have a local development environment set up with Symfony and Doctrine.
  • Have the Doctrine Migrations Bundle installed and configured.

Step-by-Step Implementation

  1. Step 1: Create the RoasterShippingRule Entity
  2. File to create: src/Entity/RoasterShippingRule.php
  3. Changes needed:

    • Create a new Doctrine entity RoasterShippingRule.
    • Add the following properties:
    • id (UUID)
    • roaster (ManyToOne relationship to Roaster)
    • shippingRegion (ManyToOne relationship to ShippingRegion)
    • excludedCountries (ManyToMany relationship to Country)
    • createdAt and updatedAt timestamps.
  4. Step 2: Update Existing Entities

  5. Files to modify: src/Entity/Roaster.php, src/Entity/ShippingRegion.php
  6. Changes needed:

    • In Roaster.php, add a OneToMany relationship to RoasterShippingRule:
      #[ORM\OneToMany(targetEntity: RoasterShippingRule::class, mappedBy: 'roaster', cascade: ['persist', 'remove'])]
      private Collection $shippingRules;
      
    • In ShippingRegion.php, add a OneToMany relationship to RoasterShippingRule:
      #[ORM\OneToMany(targetEntity: RoasterShippingRule::class, mappedBy: 'shippingRegion')]
      private Collection $roasterShippingRules;
      
  7. Step 3: Create Doctrine Migration

  8. Action: Run php bin/console make:migration to generate a new migration file.
  9. Verify: Inspect the generated migration in migrations/ to ensure it correctly creates the roaster_shipping_rule table and the necessary foreign key constraints.

  10. Step 4: Create ShipsTo DTOs

  11. File to create: src/DTO/Api/ShipsToDTO.php
  12. Changes needed:

    • Create a new readonly DTO ShipsToDTO.
    • Add the following properties:
    • type (string, either 'region' or 'country_list')
    • name (string, the name of the region or "Custom Country List")
    • countries (optional array of CountryDTOs)
  13. Step 5: Update RoasterDTO

  14. File to modify: src/DTO/Api/RoasterDTO.php
  15. Changes needed:

    • Add a new property to the constructor:
      public ?array $shipsTo = null,
      
    • This will hold an array of ShipsToDTO objects.
  16. Step 6: Update EntityToDtoMapper

  17. File to modify: src/Service/Api/Mapper/EntityToDtoMapper.php
  18. Changes needed:

    • In the mapRoasterToDto method, add logic to process the shippingRules of the Roaster entity.
    • For each RoasterShippingRule:
    • If excludedCountries is empty, create a ShipsToDTO with type = 'region', name = the region's name, and countries = null.
    • If excludedCountries is not empty, create a ShipsToDTO with type = 'country_list', name = the region's name, and countries = an array of CountryDTOs for the countries in the region minus the excluded countries.
    • Assign the resulting array of ShipsToDTOs to the shipsTo property of the RoasterDTO.
  19. Step 7: Update RoasterRepository

  20. File to modify: src/Repository/RoasterRepository.php
  21. Changes needed:
    • In executeRoasterQuery and findDtoById, update the Doctrine queries to join the new shippingRules relationship and its sub-relationships.
    • Example join:
      $qb = $this->createQueryBuilder('r')
          ->leftJoin('r.country', 'c')
          ->leftJoin('r.shippingRules', 'sr')
          ->leftJoin('sr.shippingRegion', 'sreg')
          ...
          ->addSelect('c', 'sr', 'sreg', ...)
      

Testing Strategy

  • Unit Tests:
  • Create a unit test for the EntityToDtoMapper to verify that the shippingRules are correctly transformed into the shipsTo array in various scenarios (no exclusions, with exclusions).
  • Integration Tests:
  • Create integration tests for the /api/roasters and /api/roasters/{id} endpoints.
  • The tests should:
    • Create test data with roasters, shipping regions, and shipping rules.
    • Assert that the shipsTo field is present in the JSON response and has the correct structure and data.

🎯 Success Criteria

  • The /api/roasters and /api/roasters/{id} API endpoints include a shipsTo field in the response for each roaster.
  • If a roaster ships to a region with no exclusions, the shipsTo array contains an object with type: 'region' and the region's name.
  • If a roaster ships to a region with exclusions, the shipsTo array contains an object with type: 'country_list', the region's name, and a list of the included countries.
  • The API documentation is updated to reflect the new shipsTo field.