Skip to content

Commit

Permalink
adding support to tracking cart history
Browse files Browse the repository at this point in the history
  • Loading branch information
rnlaigner committed Mar 12, 2024
1 parent af1b701 commit 8804556
Show file tree
Hide file tree
Showing 14 changed files with 130 additions and 50 deletions.
7 changes: 5 additions & 2 deletions Common/Config/AppConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

public sealed class AppConfig
{
public bool OrleansTransactions { get; set; }

public bool SellerViewPostgres { get; set; }

public bool ShipmentUpdatePostgres { get; set; }
Expand All @@ -14,7 +16,7 @@ public sealed class AppConfig

public string RedisSecondaryConnectionString { get; set; }

public bool OrleansTransactions { get; set; }
public bool TrackCartHistory { get; set; }

public bool OrleansStorage { get; set; }

Expand Down Expand Up @@ -47,6 +49,7 @@ public override string ToString()
" \nUseSwagger: " + UseSwagger +
" \nRedisReplication: " + RedisReplication +
" \nRedisPrimaryConnectionString: " + RedisPrimaryConnectionString +
" \nRedisSecondaryConnectionString: " + RedisSecondaryConnectionString;
" \nRedisSecondaryConnectionString: " + RedisSecondaryConnectionString +
" \nTrackCartHistory: " + TrackCartHistory;
}
}
11 changes: 5 additions & 6 deletions Common/Entities/Cart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,18 @@ namespace Common.Entities
public sealed class Cart
{
// no inter identified within an actor. so it requires an id
public int customerId { get; set; } = 0;
public int customerId;

public CartStatus status { get; set; } = CartStatus.OPEN;

public List<CartItem> items { get; set; } = new List<CartItem>();

public int instanceId { get; set; }

// to return
public List<ProductStatus> divergencies { get; set; }

public Cart() {}

public Cart(int customerId) {
this.customerId = customerId;
}

public override string ToString()
{
return new StringBuilder().Append("customerId : ").Append(customerId).Append("status").Append(status.ToString()).ToString();
Expand Down
48 changes: 32 additions & 16 deletions Orleans/Grains/CartActor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ namespace OrleansApp.Grains;

public class CartActor : Grain, ICartActor
{
private delegate IOrderActor GetOrderActorDelegate(int customerId);
protected readonly IPersistentState<Cart> cart;
protected readonly AppConfig config;
protected readonly bool orleansStorage;
private readonly bool trackHistory;
protected int customerId;
protected readonly ILogger<CartActor> logger;
private readonly GetOrderActorDelegate callback;
protected readonly ILogger<CartActor> logger;

private readonly Dictionary<string,List<CartItem>> history;

public CartActor([PersistentState(
stateName: "cart",
Expand All @@ -25,17 +29,18 @@ public CartActor([PersistentState(
ILogger<CartActor> _logger)
{
this.cart = state;
this.config = options;
this.callback = config.OrleansTransactions ? GetTransactionalOrderActor : GetOrderActor;
this.callback = options.OrleansTransactions ? GetTransactionalOrderActor : GetOrderActor;
this.orleansStorage = options.OrleansStorage;
this.trackHistory = options.TrackCartHistory;
if(this.trackHistory) history = new Dictionary<string, List<CartItem>>();
this.logger = _logger;
}

public override Task OnActivateAsync(CancellationToken token)
{
this.customerId = (int) this.GetPrimaryKeyLong();
if(this.cart.State is null) {
this.cart.State = new Cart();
this.cart.State.customerId = this.customerId;
this.cart.State = new Cart(this.customerId);
}
return Task.CompletedTask;
}
Expand All @@ -59,8 +64,9 @@ public virtual async Task AddItem(CartItem item)

this.cart.State.items.Add(item);

if(config.OrleansStorage)
if(this.orleansStorage){
await this.cart.WriteStateAsync();
}
}

// customer decided to checkout
Expand All @@ -71,6 +77,11 @@ public virtual async Task NotifyCheckout(CustomerCheckout customerCheckout)
var checkout = new ReserveStock(DateTime.UtcNow, customerCheckout, this.cart.State.items, customerCheckout.instanceId);
this.cart.State.status = CartStatus.CHECKOUT_SENT;
try {
if (this.trackHistory)
{
// store cart items internally
this.history.Add(customerCheckout.instanceId, new(this.cart.State.items));
}
await orderActor.Checkout(checkout);
await this.Seal();
} catch(Exception e) {
Expand All @@ -80,7 +91,20 @@ public virtual async Task NotifyCheckout(CustomerCheckout customerCheckout)
}
}

private delegate IOrderActor GetOrderActorDelegate(int customerId);
public async Task Seal()
{
this.cart.State.status = CartStatus.OPEN;
this.cart.State.items.Clear();
if(this.orleansStorage)
await this.cart.WriteStateAsync();
}

public Task<List<CartItem>> GetHistory(string tid)
{
if(this.history.ContainsKey(tid))
return Task.FromResult(this.history[tid]);
return Task.FromResult(new List<CartItem>());
}

private IOrderActor GetOrderActor(int customerId)
{
Expand All @@ -92,12 +116,4 @@ private ITransactionalOrderActor GetTransactionalOrderActor(int customerId)
return this.GrainFactory.GetGrain<ITransactionalOrderActor>(customerId);
}

public async Task Seal()
{
this.cart.State.status = CartStatus.OPEN;
this.cart.State.items.Clear();
if(this.config.OrleansStorage)
await this.cart.WriteStateAsync();
}

}
1 change: 1 addition & 0 deletions Orleans/Grains/Replication/CausalCartActor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public override async Task NotifyCheckout(CustomerCheckout customerCheckout)
// process new prices as discount
if (item.UnitPrice < productReplica.Price)
{
item.UnitPrice = productReplica.Price;
item.Voucher += productReplica.Price - item.UnitPrice;
}
}
Expand Down
9 changes: 8 additions & 1 deletion Orleans/Grains/Replication/EventualCartActor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,13 @@ public async Task StopConsuming()

private Task UpdateProductAsync(Product product, StreamSequenceToken token)
{
this.cachedProducts.Add((product.seller_id, product.product_id), product);
if(this.cachedProducts.ContainsKey((product.seller_id, product.product_id))){
this.cachedProducts[(product.seller_id, product.product_id)] = product;
} else
{
this.cachedProducts.Add((product.seller_id, product.product_id), product);
}

return Task.CompletedTask;
}

Expand All @@ -75,6 +81,7 @@ public override async Task NotifyCheckout(CustomerCheckout customerCheckout)
{
Product product = this.cachedProducts[ID];
if( item.Version.SequenceEqual(product.version) && item.UnitPrice < product.price ){
item.UnitPrice = product.price;
item.Voucher += product.price - item.UnitPrice;
}
}
Expand Down
20 changes: 10 additions & 10 deletions Orleans/Interfaces/ICartActor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
using Common.Requests;
using Orleans.Concurrency;

namespace OrleansApp.Interfaces
namespace OrleansApp.Interfaces;

public interface ICartActor : IGrainWithIntegerKey
{
public Task AddItem(CartItem item);

public interface ICartActor : IGrainWithIntegerKey
{
public Task AddItem(CartItem item);
public Task NotifyCheckout(CustomerCheckout basketCheckout);

public Task NotifyCheckout(CustomerCheckout basketCheckout);
[ReadOnly]
public Task<Cart> GetCart();

[ReadOnly]
public Task<Cart> GetCart();
public Task Seal();

public Task Seal();
}
}
public Task<List<CartItem>> GetHistory(string tid);
}
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ Further details about the benchmark can be found in the benchmark driver [reposi

The Orleans virtual actor programming model prescribes a single thread per actor. Since we have one event per function call, to minimize latency, we map each entity to a logical actor, e.g., order, payment, and shipment.

* A cart actor per customer. ID is customer_id
* A customer actor per customer. ID is customer_id
* A cart actor per customer. ID is `customer_id`
* A customer actor per customer. ID is `customer_id`
* A product actor per product. ID is composite `[seller_id,product_id]`
* A seller actor per seller. ID is seller_id
* A seller actor per seller. ID is `seller_id`
* A stock actor per stock item. ID is composite `[seller_id,product_id]`
* An order actor per customer. ID is customer_id
* A payment actor per customer. ID is customer_id
* An order actor per customer. ID is `customer_id`
* A payment actor per customer. ID is `customer_id`
* A shipment actor per partition of customers. Hash to define which shipment actor an order is forwarded to is defined by the hash of `[customer_id]`. Number of partitions is predefined (see [Configuration](#config)).

Actors that log historical records:
Expand Down
21 changes: 20 additions & 1 deletion Silo/Controllers/CartController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ public async Task<ActionResult> NotifyCheckout([FromServices] IGrainFactory grai
{
await cartGrain.NotifyCheckout(customerCheckout);
return Ok();
} catch(Exception e)
}
catch(Exception e)
{
return StatusCode((int)HttpStatusCode.InternalServerError, e.Message);
}
Expand All @@ -82,5 +83,23 @@ public async Task<ActionResult> Seal([FromServices] IGrainFactory grains, int cu
return StatusCode((int)HttpStatusCode.InternalServerError, e.Message);
}
}

[Route("/cart/{customerId}/history/{tid}")]
[HttpGet]
[ProducesResponseType((int)HttpStatusCode.Accepted)]
[ProducesResponseType((int)HttpStatusCode.InternalServerError)]
public async Task<ActionResult<Dictionary<string,List<CartItem>>>> GetHistory([FromServices] IGrainFactory grains, int customerId, string tid)
{
var cartGrain = this.callback(grains, customerId);
try
{
return Ok(await cartGrain.GetHistory(tid));
}
catch (Exception e)
{
return StatusCode((int)HttpStatusCode.InternalServerError, e.Message);
}
}

}

6 changes: 5 additions & 1 deletion Silo/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
var redisPrimaryConnectionString = configSection.GetValue<string>("RedisPrimaryConnectionString");
var redisSecondaryConnectionString = configSection.GetValue<string>("RedisSecondaryConnectionString");

var trackCartHistory = configSection.GetValue<bool>("TrackCartHistory");

AppConfig appConfig = new()
{
OrleansTransactions = orleansTransactions,
Expand All @@ -44,6 +46,7 @@
NumShipmentActors = numShipmentActors,
UseDashboard = useDash,
UseSwagger = useSwagger,
TrackCartHistory = trackCartHistory
};

// Orleans testing has no support for IOptions apparently...
Expand Down Expand Up @@ -206,7 +209,8 @@
" \n Stream Replication: " + appConfig.StreamReplication +
" \n RedisReplication: " + appConfig.RedisReplication +
" \n RedisPrimaryConnectionString: "+ appConfig.RedisPrimaryConnectionString +
" \n RedisSecondaryConnectionString: "+ appConfig.RedisSecondaryConnectionString
" \n RedisSecondaryConnectionString: "+ appConfig.RedisSecondaryConnectionString +
" \n TrackCartHistory: "+appConfig.TrackCartHistory
);
Console.WriteLine(" The Orleans server started. Press any key to terminate... ");
Console.WriteLine("\n *************************************************************************");
Expand Down
3 changes: 2 additions & 1 deletion Silo/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"StreamReplication": false,
"RedisReplication": false,
"RedisPrimaryConnectionString": "localhost:6379",
"RedisSecondaryConnectionString": "localhost:6380"
"RedisSecondaryConnectionString": "localhost:6380",
"TrackCartHistory": true
}
}

3 changes: 2 additions & 1 deletion Silo/appsettings.Production.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"StreamReplication": false,
"RedisReplication": false,
"RedisPrimaryConnectionString": "localhost:6379",
"RedisSecondaryConnectionString": "localhost:6380"
"RedisSecondaryConnectionString": "localhost:6380",
"TrackCartHistory": true
}
}

3 changes: 2 additions & 1 deletion Test/Infra/BaseTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ protected CustomerCheckout BuildCustomerCheckout(int customerId)
CardExpiration = "1224",
CardSecurityNumber = "001",
CardBrand = "VISA",
Installments = 1
Installments = 1,
instanceId = customerId.ToString()
};
return customerCheckout;
}
Expand Down
2 changes: 2 additions & 0 deletions Test/Infra/ConfigHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class ConfigHelper
NumShipmentActors = 1,
UseDashboard = false,
UseSwagger = false,
TrackCartHistory = true
};

public static AppConfig NonTransactionalDefaultAppConfig = new()
Expand All @@ -30,6 +31,7 @@ public class ConfigHelper
NumShipmentActors = 1,
UseDashboard = false,
UseSwagger = false,
TrackCartHistory = false
};

public const string PostgresConnectionString = "Host=localhost;Port=5432;Database=postgres;Username=postgres;Password=password;Pooling=true;Minimum Pool Size=0;Maximum Pool Size=10000";
Expand Down
Loading

0 comments on commit 8804556

Please sign in to comment.