php – Identifying Aggregate Root and Services Logic Responsibility

I’m attempting to refactor small meal planner following DDD, but I’m completely lost and overwhelmed in identifying the separation of concerns within my domain models.

Here’s the business requirement: A user can generate a 14 day plan consisting of 3 meals per day (collection of 42 Meal), they must provide their body info along with their meal plan preferences before they’re allowed to generate a plan. Their body info is only required to calculate the optimal caloric range per meal, whilst the meal preferences are mostly for sorting the recipes (provided from the subdomain).

I’ve came up with the following:

src
|-- App
|   `-- MealPlan
|       `-- Application
|       |   `-- Create
|       |   |   |-- CreateMealPlanService.php
|       |   `-- Find
|       `-- Domain
|       |   |-- Exception
|       |   |-- Services
|       |   |   |-- BuildMealsService.php
|       |   `-- Meal
|       |   |   |-- Meal.php
|       |   |   |-- MealId.php // uuid 
|       |   |   |-- Ingredients.php // VO
|       |   |   |-- Macros.php // vo
|       |   |   |-- Meals.php
|       |   `-- Profile
|       |   |   |-- Gender.php
|       |   |   |-- Height.php
|       |   |   |-- Unit.php
|       |   |   |-- Weight.php
|       |   |   |-- UserId.php // User Id from third party service sent through http requests
|       |   |   |-- Profile.php // ValueObject
|       |   `-- Preferences 
|       |   |   |-- Allergen.php
|       |   |   |-- Intolerance.php
|       |   |   |-- Preferences.php // ValueObject
|       |   |-- MealPlan.php // The aggregate root
|       |   |-- MealPlanId.php // uuid
|       |   |-- MealPlanRepositoryInterface.php
|       `-- Infrastructure
|           `-- Http
|           `-- Persistence
|               `--MealPlanRepository.php
`-- Recipes
|   `-- Application
|   `-- Domain
|   `-- Infrastructure
|
`-- Shared
    `-- Domain
    `-- Infrastructure

Use case: after authentication, during onboarding stage Profile and Preferences form data gets sent to CreateMealPlanService.

At this stage, I’m not sure if CreateMealPlanService should call BuildMealsService or if that logic should be within the MealPlan aggragate root, obviously a user MealPlan belongs to a user (tgethe UserId I put in the Profile as a VO, but I also think the MealPlan should have that UserId reference…).

I figured BuildMealsService would inject the RecipesService (from the Recipes Bounded Context), and there it’d generate the 42 meals, and passed to the MealPlan to be saved, but I’m not sure anymore at this point whose responsibility it really is.

Here’s the MealPlan aggragate root:

final class MealPlan {

    public function __construct(
        private MealPlanId $id,
        private Profile $profile,
        private Preferences $preferences,
        private Meals $meals
    ){}

    public static function create(MealPlanId $id, Profile $profile, Preferences $preferences){
        return new self($id, $profile, $preferences);
    }

    public function id(): MealPlanId {
        return $this->id;
    }

    public function profile(): Profile {
        return $this->profile;
    }

    public function preferences(): Preferences {
        return $this->preferences;
    }

    public function meals(): Meals {
        return $this->meals;
    }

}