Skip to content

Commit

Permalink
implement invariants
Browse files Browse the repository at this point in the history
EnOSeChi committed Mar 5, 2021
1 parent 28931f9 commit ceda4ee
Showing 8 changed files with 144 additions and 22 deletions.
7 changes: 7 additions & 0 deletions DDDInPractice.Application/DDDInPracticeDbContext.cs
Original file line number Diff line number Diff line change
@@ -8,6 +8,8 @@ public class DDDInPracticeDbContext : DbContext
private readonly string _connectionString;

public DbSet<SnackMachine> SnackMachines { get; set; }
public DbSet<Snack> Snacks { get; set; }
public DbSet<Slot> Slots { get; set; }

public DDDInPracticeDbContext(string connectionString)
{
@@ -28,6 +30,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
e.Ignore(x => x.MoneyInTransaction);
});

modelBuilder.Entity<Slot>(e =>
{
e.OwnsOne(x => x.SnackPile);
});

base.OnModelCreating(modelBuilder);
}
}
2 changes: 1 addition & 1 deletion DDDInPractice.Application/SnackMachineViewModel.cs
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ public void ReturnMoney()

public void BuySnack()
{
_snackMachine.BuySnack();
_snackMachine.BuySnack(1);
using (var context = ContextFactory.DefaultContext())
using (var transaction = context.Database.BeginTransaction())
{
61 changes: 57 additions & 4 deletions DDDInPractice.Logic.Tests/SnackMachineSpecs.cs
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ public void Return_money_empies_money_in_transaction()

snackMachine.ReturnMoney();

snackMachine.MoneyInTransaction.Amount.Should().Be(0m);
snackMachine.MoneyInTransaction.Should().Be(0m);
}

[Fact]
@@ -28,7 +28,7 @@ public void Inserted_money_goes_to_money_in_transaction()
snackMachine.InsertMoney(Money.OneCent);
snackMachine.InsertMoney(Money.OneDollar);

snackMachine.MoneyInTransaction.Amount.Should().Be(1.01m);
snackMachine.MoneyInTransaction.Should().Be(1.01m);
}

[Fact]
@@ -51,9 +51,62 @@ public void BuySnack_trades_inserted_money_for_a_snack()

snackMachine.BuySnack(1);

snackMachine.MoneyInTransaction.Should().Be(Money.None);
snackMachine.MoneyInside.Amount.Should().Be(2m);
snackMachine.MoneyInTransaction.Should().Be(0);
snackMachine.MoneyInside.Amount.Should().Be(1m);
snackMachine.GetSnackPile(1).Quantity.Should().Be(9);
}

[Fact]
public void Cannot_make_purchase_when_there_is_no_snacks()
{
var snackMachine = new SnackMachine();

Action action = () => snackMachine.BuySnack(1);

action.Should().Throw<InvalidOperationException>();
}

[Fact]
public void Cannot_make_purchase_when_if_not_enough_money_inserted()
{
var snackMachine = new SnackMachine();
snackMachine.LoadSnack(1, new SnackPile(new Snack("Some snack"), 1, 2m));
snackMachine.InsertMoney(Money.OneDollar);

Action action = () => snackMachine.BuySnack(1);

action.Should().Throw<InvalidOperationException>();
}

[Fact]
public void Returns_money_with_highest_denomination_first()
{
var snackMachine = new SnackMachine();
snackMachine.LoadMoney(Money.OneDollar);

snackMachine.InsertMoney(Money.Quarter);
snackMachine.InsertMoney(Money.Quarter);
snackMachine.InsertMoney(Money.Quarter);
snackMachine.InsertMoney(Money.Quarter);

snackMachine.ReturnMoney();

snackMachine.MoneyInside.QuarterCount.Should().Be(4);
snackMachine.MoneyInside.OneDollarCount.Should().Be(0);
}

[Fact]
public void After_purchase_change_is_returned()
{
var snackMachine = new SnackMachine();
snackMachine.LoadSnack(1, new SnackPile(new Snack("Some snack"), 1, 0.5m));
snackMachine.LoadMoney(Money.TenCent * 10);

snackMachine.InsertMoney(Money.OneDollar);
snackMachine.BuySnack(1);

snackMachine.MoneyInside.Amount.Should().Be(1.5m);
snackMachine.MoneyInTransaction.Should().Be(0);
}
}
}
6 changes: 4 additions & 2 deletions DDDInPractice.Logic.Tests/TemporaryTests.cs
Original file line number Diff line number Diff line change
@@ -17,8 +17,9 @@ public void Test()
{
ctx.Database.EnsureCreated();
var snackMachine = new SnackMachine();
snackMachine.LoadSnack(1, new SnackPile(new Snack("Chips"), 10, 0.01m));
snackMachine.InsertMoney(Money.OneCent);
snackMachine.BuySnack();
snackMachine.BuySnack(1);

ctx.SnackMachines.Add(snackMachine);
ctx.SaveChanges();
@@ -42,8 +43,9 @@ public void Test()
public void Test2()
{
var snackMachine = new SnackMachine();
snackMachine.LoadSnack(1, new SnackPile(new Snack("Chips"), 10, 0.01m));
snackMachine.InsertMoney(Money.OneCent);
snackMachine.BuySnack();
snackMachine.BuySnack(1);

using (var ctx = ContextFactory.DefaultContext())
{
39 changes: 39 additions & 0 deletions DDDInPractice.Logic/Money.cs
Original file line number Diff line number Diff line change
@@ -43,6 +43,34 @@ public Money(
TwentyDollarCount = twentyDollarCount;
}

internal Money Allocate(decimal amount)
{
int twentyDollarCount = Math.Min((int)(amount / 20), TwentyDollarCount);
amount = amount - twentyDollarCount * 20;

int fiveDollarCount = Math.Min((int)(amount / 5), FiveDollarCount);
amount = amount - fiveDollarCount * 5;

int oneDollarCount = Math.Min((int)(amount / 1), OneDollarCount);
amount = amount - oneDollarCount * 1;

int quarterCount = Math.Min((int)(amount / 0.25m), QuarterCount);
amount = amount - quarterCount * 0.25m;

int tenCentCount = Math.Min((int)(amount / 0.1m), TenCentCount);
amount = amount - tenCentCount * 0.1m;

int oneCentCount = Math.Min((int)(amount / 0.01m), OneCentCount);

return new Money(
oneCentCount,
tenCentCount,
quarterCount,
oneDollarCount,
fiveDollarCount,
twentyDollarCount);
}

// private set for ef core
public int OneCentCount { get; private set; }
public int TenCentCount { get; private set; }
@@ -92,6 +120,17 @@ protected override IEnumerable<object> GetEqualityComponents()
money1.TwentyDollarCount - money2.TwentyDollarCount);
}

public static Money operator *(Money money1, int multipier)
{
return new Money(
money1.OneCentCount * multipier,
money1.TenCentCount * multipier,
money1.QuarterCount * multipier,
money1.OneDollarCount * multipier,
money1.FiveDollarCount * multipier,
money1.TwentyDollarCount * multipier);
}

public override string ToString()
{
if (Amount < 1)
4 changes: 3 additions & 1 deletion DDDInPractice.Logic/Slot.cs
Original file line number Diff line number Diff line change
@@ -6,7 +6,9 @@ namespace DDDInPractice.Logic
{
public class Slot : Entity
{
public Slot(SnackMachine snackMachine, int position)
private Slot() { }

public Slot(SnackMachine snackMachine, int position) : this()
{
SnackPile = new SnackPile(null, 0, 0m);
SnackMachine = snackMachine;
37 changes: 27 additions & 10 deletions DDDInPractice.Logic/SnackMachine.cs
Original file line number Diff line number Diff line change
@@ -10,8 +10,8 @@ public sealed class SnackMachine : AggregateRoot
// how much machine have
public Money MoneyInside { get; private set; } = Money.None;
// how much is inserted by user
public Money MoneyInTransaction { get; private set; } = Money.None;
private IList<Slot> Slots { get; private set; }
public decimal MoneyInTransaction { get; private set; } = 0;
public IList<Slot> Slots { get; private set; }

public SnackMachine()
{
@@ -37,20 +37,32 @@ public void InsertMoney(Money money)
if (!coinsAndNotes.Contains(money))
throw new InvalidOperationException();

MoneyInTransaction += money;
MoneyInTransaction += money.Amount;
MoneyInside += money;
}

public void ReturnMoney()
{
MoneyInTransaction = Money.None;
Money moneyToReturn = MoneyInside.Allocate(MoneyInTransaction);
MoneyInside -= moneyToReturn;
MoneyInTransaction = 0;
}

public void BuySnack(int position)
{
Slot slot = GetSlot(position);

if (slot.SnackPile.Price > MoneyInTransaction)
throw new InvalidOperationException();

slot.SnackPile = slot.SnackPile.SubtractOne();
MoneyInside += MoneyInTransaction;
MoneyInTransaction = Money.None;

Money change = MoneyInside.Allocate(MoneyInTransaction - slot.SnackPile.Price);
if (change.Amount < MoneyInTransaction - slot.SnackPile.Price)
throw new InvalidOperationException();

MoneyInside -= change;
MoneyInTransaction = 0;
}

public void LoadSnack(int position, SnackPile snackPile)
@@ -59,14 +71,19 @@ public void LoadSnack(int position, SnackPile snackPile)
slot.SnackPile = snackPile;
}

private Slot GetSlot(int position)
public SnackPile GetSnackPile(int position)
{
return Slots.Single(x => x.Position == position);
return GetSlot(position).SnackPile;
}

public SnackPile GetSnackPile(int position)
public void LoadMoney(Money oneDollar)
{
return GetSlot(position).SnackPile;
MoneyInside += oneDollar;
}

private Slot GetSlot(int position)
{
return Slots.Single(x => x.Position == position);
}
}
}
10 changes: 6 additions & 4 deletions DDDInPractice.Logic/SnackPile.cs
Original file line number Diff line number Diff line change
@@ -6,7 +6,9 @@ namespace DDDInPractice.Logic
{
public sealed class SnackPile : ValueObject
{
public SnackPile(Snack snack, int quantity, decimal price)
private SnackPile() { }

public SnackPile(Snack snack, int quantity, decimal price) : this()
{
if (quantity < 0)
throw new InvalidOperationException();
@@ -20,9 +22,9 @@ public SnackPile(Snack snack, int quantity, decimal price)
Price = price;
}

public Snack Snack { get; }
public int Quantity { get; }
public decimal Price { get; }
public Snack Snack { get; private set; }
public int Quantity { get; private set; }
public decimal Price { get; private set; }

protected override IEnumerable<object> GetEqualityComponents()
{

0 comments on commit ceda4ee

Please sign in to comment.