title: Retrieving aggregates from inside of other aggregates
body: <p>I have been studying DDD for over year now, but I'm still quite unhappy with my aggregate understanding. I have prepared a complicated use case example in python where some issues with aggregates arises.</p>
  
  <p>Use case: Player can order his unit to attack other unit, he chooses with what power attack will be made. After attack, owner of attacked unit is notified of that fact.</p>
  
  <p>My problem is that when unit attacks another unit in domain logic, I have access to only those unit aggregates, but to compute damage done on attack I need access to aggregates referenced by id on these units. In particular for Weapon and Armor aggregates (which are ARs because they can exist without unit and I would like to track their history).</p>
  
  <p>I have two options:</p>
  
  <ol>
  <li>load these aggregate in domain service and pass as arguments in function call:</li>
  </ol>
  
  <p><code>unit.attack_other_unit_with_power(unit_being_attacked, power, weapon, armor)
  </code></p>
  
  <p>but it looks really bad.</p>
  
  <ol start="2">
  <li>Load weapon and armor aggregates from inside of unit aggregate.</li>
  </ol>
  
  <p>I have prepared code to present that approach.</p>
  
  <p>Application service.</p>
  
  <pre><code>
  """
  Game application services.
  """
  
  from game.domain.model.attackpower import AttackPower
  from game.domain.exception import PlayerNotOwnerOfUnit, UnitCannotMeleeAttack
  
  
  class GameService(object):
      """
      Game application services.
      """
      def __init__(self, player_repository, unit_repository):
          """
          Init.
  
          :param PlayerRepository player_reposistory: Player repository.
          :param UnitRepository unit_reposistory: Unit repository.
          """
          self._player_repository = player_repository
          self._unit_repository = unit_repository
  
      def player_order_unit_to_melee_attack_another_unit_using_power(
          self, player_id, unit_id, unit_being_attacked_id, power
      ):
          """
          Player order his unit to melee attack other unit, using given power.
  
          :param int player_id: Player id.
          :param int unit_id: Player unit id.
          :param int unit_being_attacked_id: Id of unit that is being attacked.
          :param float power: Power percentage value .
          """
          player = self._player_repository.get_by_id(player_id)
          unit = self._unit_repository.get_by_id(unit_id)
          unit_being_attacked = self._unit_repository.get_by_id(unit_being_attacked_id)
          attack_power = AttackPower(power)
  
          if not self._is_player_owner_of_unit(player, unit):
              raise PlayerNotOwnerOfUnit(player, unit)
          if not unit.can_melee_attack():
              raise UnitCannotMeleeAttack(unit)
          unit.melee_attack_unit_using_power(unit_being_attacked, attack_power)
  
          self._unit_repository.store(unit)
          self._unit_repository.store(unit_being_attacked)
  </code></pre>
  
  <p>Unit aggregate.</p>
  
  <pre><code>
  from game.domain.model.health import Health
  from game.domain.model.event.unitwasattacked import UnitWasAttacked
  from game.domain.service.damage import calculate_damage
  
  
  class Unit(object):
      """
      Unit aggregate.
      """
      def __init__(self, id, owner_id, player_repository, weapon_repository, armor_repository, event_dispatcher):
          """
          Init.
  
          :param int id: Id of this unit.
          :param int owner_id: Id of player that is owner of this unit.
          :param PlayerRepository player_repository: Player repository implementation.
          :param WeaponRepository weapon_repository: Weapon repository implementation.
          :param ArmorRepository armor_repository: Armor repository implementation.
          :param EventDispatcher event_dispatcher: Event dispatcher.
          """
          self._id = id
          self._owner_id = owner_id
          self._health = Health(100.0)
          self._weapon_id = None
          self._armor_id = None
          self._player_repository = player_repository
          self._weapon_repository = weapon_repository
          self._armor_repository = armor_repository
          self._event_dispatcher = event_dispatcher
  
      def id(self):
          """
          Get unit id.
  
          :return: int
          """
          return self._id
  
      def can_melee_attack(self):
          """
          Check if unit can melee attack.
  
          :return: bool
          """
          if self._is_fighting_bare_hands():
              return True
          weapon = self._weapon_repository.get_by_id(self._weapon_id)
          if weapon.is_melee():
              return True
          return False
  
      def _is_fighting_bare_hands(self):
          """
          Check if unit is fighting with bare hands (no weapon).
  
          :return: bool
          """
          return self.has_weapon()
  
      def has_weapon(self):
          """
          Check if unit has weapon equipped.
  
          :return: bool
          """
          if self._weapon_id is None:
              return False
          return True
  
      def melee_attack_unit_using_power(self, attacked_unit, attack_power):
          """
          Melee attack other unit using given attack power.
  
          :param Unit attacked_unit: Unit being attacked.
          :param AttackPower attack_power: Attack power.
          """
          weapon = self.weapon()
          armor = attacked_unit.armor()
  
          damage = calculate_damage(weapon, armor, attack_power)
          attacked_unit.deal_damage(damage)
  
          self._notify_unit_owner_of_attack(attacked_unit)
  
      def _notify_unit_owner_of_attack(self, unit):
          """
          Notify owner of given unit that his unit was attacked.
  
          :param Unit unit: Attacked unit.
          """
          unit_owner = unit.owner()
          unit_was_attacked = UnitWasAttacked(unit.id(), unit_owner.id())
          self._event_dispatcher.dispatch(unit_was_attacked)
  
      def owner(self):
          """
          Get owner aggregate.
  
          :return: Player
          """
          return self._player_repository.get_by_id(self._owner_id)
  
      def armor(self):
          """
          Get armor object.
  
          :return: Armor
          """
          if self._armor_id is None:
              return None
          return self._armor_repository.get_by_id(self._armor_id)
  
      def weapon(self):
          """
          Get weapon object.
  
          :return: Weapon
          """
          if self._weapon_id is None:
              return None
          return self._weapon_repository.get_by_id(self._weapon_id)
  
      def deal_damage(self, damage):
          """
          Deal given damage to self.
  
          :param Damage damage: Dealt damage.
          """
          self._health.take_damage(damage)
  </code></pre>
  
  <p>Question is if this is ok to access reposistory from inside of aggregate only for read (no storing)?
  What if I would like to take armor of unit and do some changes to it and then store.</p>
  
  <p><code>armor = unit.armor()  # loaded using repository internally
  armor.repair()
  armor_repository.store(armor)
  </code></p>
  
  <p>Is it violating anything or would that cause problems?</p>
  
  <p>If you have any other remarks for this code I would be glad to hear it. </p>
  
  <p><strong>UPDATE</strong>: I've found another problem. What if I would like to degrade weapon quality after each attack?
  I would have to change weapon state and store it, but storing from insisde of aggregate is a bad idea, because we have no control over it.</p>