02-08-2019, 01:25 AM
J'ai terminé le code pour charger/décharger un convoi. Je suis assez content du résultat. Voici les nouveaux cas que je couvre :
Pour représenter les quantités de ressources, je raisonne avec des maps qui sont comme des value objects. J'ai créé des fonctions add et substract pour manipuler ces value objects.
Ainsi, je peux m'en servir pour charger/décharger mes convois :
Une fois que la fonction de décision
Il retourne donc le même état
Citation :ConvoysTest
* test Convoy can't be loaded if resources are missing (0.8ms)
* test Only loaded resources can be unloaded from the convoy (6.1ms)
* test Loaded resources are moved from the territory to the convoy (1.0ms)
* test Nonexistent convoy can't be loaded with resources (0.6ms)
* test Nonexistent convoy can't be unloaded (0.5ms)
* test Unloaded resources are moved from the convoy to the territory (11.4ms)
Pour représenter les quantités de ressources, je raisonne avec des maps qui sont comme des value objects. J'ai créé des fonctions add et substract pour manipuler ces value objects.
defmodule Seelies.ResourcesQuantity do
def null do
%{
gold: 0,
silver: 0,
bronze: 0
}
end
def has_enough?(available_quantity, needed_quantity) do
Enum.all?(needed_quantity, fn ({resource_type, quantity}) ->
available_quantity[resource_type] <= quantity
end)
end
def add(base_quantity, added_quantity) do
Map.merge(base_quantity, added_quantity, fn (_resource_type, count, other_count) ->
count + other_count
end)
end
def substract(base_quantity, substracted_quantity) do
Enum.reduce(substracted_quantity, base_quantity, fn ({resource_type, count}, remaining_quantity) ->
Map.update!(remaining_quantity, resource_type, fn (initial_count) -> initial_count - count end)
end)
end
end
Ainsi, je peux m'en servir pour charger/décharger mes convois :
defmodule Seelies.ResourcesLoadedIntoConvoy do
@derive Jason.Encoder
defstruct [:game_id, :convoy_id, :resources]
def apply(game = %Seelies.Game{game_id: game_id, convoys: convoys, territories: territories}, %Seelies.ResourcesLoadedIntoConvoy{game_id: game_id, convoy_id: convoy_id, resources: resources}) do
%{game |
convoys: update_in(convoys, [convoy_id, :resources], fn (carried_resources) -> Seelies.ResourcesQuantity.add(carried_resources, resources) end),
territories: update_in(territories, [convoys[convoy_id].territory_id, :resources], fn (stored_resources) -> Seelies.ResourcesQuantity.substract(stored_resources, resources) end)}
end
end
defmodule Seelies.LoadResourcesIntoConvoy do
defstruct [:game_id, :resources, :convoy_id]
def execute(%Seelies.Game{game_id: game_id, convoys: convoys, territories: territories}, %Seelies.LoadResourcesIntoConvoy{convoy_id: convoy_id, resources: resources}) do
cond do
convoys[convoy_id] == nil ->
{:error, :convoy_not_found}
Seelies.ResourcesQuantity.has_enough?(territories[convoys[convoy_id].territory_id].resources, resources) ->
{:error, :not_enough_resources}
true ->
%Seelies.ResourcesLoadedIntoConvoy{game_id: game_id, convoy_id: convoy_id, resources: resources}
end
end
end
Une fois que la fonction de décision
execute
de l'action LoadResourcesIntoConvoy
a validé que tout était en ordre, elle émet l'événement ResourcesLoadedIntoConvoy
. Quand il reçoit cet événement, l'aggregate peut changer d'état grâce à sa fonction de mutation apply
. Il retourne donc le même état
game
(qui arrive en premier argument), mais en changeant les clés convoys
et territories
. Dans chacune de ces deux clés on trouve des maps dans lesquelles ont associe respectivement des informations à l'id d'un convoi ou à l'id d'un territoire. On modifie donc uniquement le convoi et le territoire concernés grâce à la fonction update_in
qui permet de plonger en profondeur dans des maps imbriqués et on met à jour les quantités de ressources grâce aux fonctions citées plus haut.