Fixing cache implementation
This commit is contained in:
parent
3262ff82b0
commit
c3c76effea
8 changed files with 99 additions and 86 deletions
13
.vscode/launch.json
vendored
13
.vscode/launch.json
vendored
|
@ -8,8 +8,7 @@
|
||||||
"name": ".NET Core Launch (console)",
|
"name": ".NET Core Launch (console)",
|
||||||
"type": "coreclr",
|
"type": "coreclr",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"preLaunchTask": "build",
|
"preLaunchTask": "build service",
|
||||||
// If you have changed target frameworks, make sure to update the program path.
|
|
||||||
"program": "${workspaceFolder}/src/cartservice/bin/Debug/netcoreapp2.0/cartservice.dll",
|
"program": "${workspaceFolder}/src/cartservice/bin/Debug/netcoreapp2.0/cartservice.dll",
|
||||||
"args": [],
|
"args": [],
|
||||||
"cwd": "${workspaceFolder}/src/cartservice",
|
"cwd": "${workspaceFolder}/src/cartservice",
|
||||||
|
@ -18,6 +17,16 @@
|
||||||
"stopAtEntry": false,
|
"stopAtEntry": false,
|
||||||
"internalConsoleOptions": "openOnSessionStart"
|
"internalConsoleOptions": "openOnSessionStart"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": ".NET Core Launch (Tests)",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "build test",
|
||||||
|
"program": "${workspaceRoot}/tests/cartservice/bin/Debug/netcoreapp2.0/cartservice.tests.dll",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceRoot}/tests/cartservice",
|
||||||
|
"stopAtEntry": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": ".NET Core Attach",
|
"name": ".NET Core Attach",
|
||||||
"type": "coreclr",
|
"type": "coreclr",
|
||||||
|
|
20
.vscode/tasks.json
vendored
20
.vscode/tasks.json
vendored
|
@ -1,14 +1,32 @@
|
||||||
{
|
{
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
|
"args": [],
|
||||||
|
"options": {
|
||||||
|
"cwd": "${workspaceRoot}"
|
||||||
|
},
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"label": "build",
|
"label": "build service",
|
||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"type": "process",
|
"type": "process",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"${workspaceFolder}/src/cartservice/cartservice.csproj"
|
"${workspaceFolder}/src/cartservice/cartservice.csproj"
|
||||||
],
|
],
|
||||||
|
"problemMatcher": "$msCompile",
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "build test",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"${workspaceFolder}/tests/cartservice/cartservice.tests.csproj"
|
||||||
|
],
|
||||||
"problemMatcher": "$msCompile"
|
"problemMatcher": "$msCompile"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -32,54 +32,9 @@ namespace cartservice
|
||||||
return Empty;
|
return Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async override Task<Hipstershop.Cart> GetCart(GetCartRequest request, ServerCallContext context)
|
public override Task<Hipstershop.Cart> GetCart(GetCartRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
return await cartStore.GetCartAsync(request.UserId);
|
return cartStore.GetCartAsync(request.UserId);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static class CartUtils
|
|
||||||
{
|
|
||||||
public static Hipstershop.Cart ToHipsterCart(this Cart cart)
|
|
||||||
{
|
|
||||||
var hipsterCart = new Hipstershop.Cart
|
|
||||||
{
|
|
||||||
UserId = cart.UserId,
|
|
||||||
Items = { cart.Items.Select(i => new CartItem { ProductId = i.Key, Quantity = i.Value }) }
|
|
||||||
};
|
|
||||||
return hipsterCart;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Actual implementation of the cart
|
|
||||||
internal class Cart
|
|
||||||
{
|
|
||||||
// Maps between productId and its quantity
|
|
||||||
private Dictionary<string, int> cart = new Dictionary<string, int>();
|
|
||||||
|
|
||||||
public Cart(string userId)
|
|
||||||
{
|
|
||||||
UserId = userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string UserId { get; set; }
|
|
||||||
|
|
||||||
public void AddItem(string productId, int quantity)
|
|
||||||
{
|
|
||||||
cart.Add(productId, quantity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EmptyCart()
|
|
||||||
{
|
|
||||||
cart.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IReadOnlyDictionary<string, int> Items
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return cart;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -34,6 +34,7 @@ namespace cartservice
|
||||||
// The busy wait is because when we run in a container, we can't use techniques such as waiting on user input (Console.Readline())
|
// The busy wait is because when we run in a container, we can't use techniques such as waiting on user input (Console.Readline())
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
|
//var store = new LocalCartStore();
|
||||||
var store = new RedisCartStore(redisAddress);
|
var store = new RedisCartStore(redisAddress);
|
||||||
Server server = new Server
|
Server server = new Server
|
||||||
{
|
{
|
||||||
|
@ -50,8 +51,6 @@ namespace cartservice
|
||||||
{
|
{
|
||||||
Thread.Sleep(TimeSpan.FromMinutes(10));
|
Thread.Sleep(TimeSpan.FromMinutes(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
|
|
|
@ -1,41 +1,52 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using cartservice.interfaces;
|
using cartservice.interfaces;
|
||||||
|
using Hipstershop;
|
||||||
|
|
||||||
namespace cartservice.cartstore
|
namespace cartservice.cartstore
|
||||||
{
|
{
|
||||||
internal class LocalCartStore
|
internal class LocalCartStore : ICartStore
|
||||||
{
|
{
|
||||||
// Maps between user and their cart
|
// Maps between user and their cart
|
||||||
private ConcurrentDictionary<string, Cart> userCartItems = new ConcurrentDictionary<string, Cart>();
|
private ConcurrentDictionary<string, Hipstershop.Cart> userCartItems = new ConcurrentDictionary<string, Hipstershop.Cart>();
|
||||||
|
|
||||||
public void AddItem(string userId, string productId, int quantity)
|
public Task AddItemAsync(string userId, string productId, int quantity)
|
||||||
{
|
{
|
||||||
Cart cart;
|
Console.WriteLine($"AddItemAsync called with userId={userId}, productId={productId}, quantity={quantity}");
|
||||||
|
var newCart = new Hipstershop.Cart
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
Items = { new Hipstershop.CartItem { ProductId = productId, Quantity = quantity } }
|
||||||
|
};
|
||||||
|
userCartItems.AddOrUpdate(userId, newCart,
|
||||||
|
(k, exVal) =>
|
||||||
|
{
|
||||||
|
// Currently we assume that we only add to the cart
|
||||||
|
exVal.Items.Add(new Hipstershop.CartItem { ProductId = productId, Quantity = quantity });
|
||||||
|
return exVal;
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task EmptyCartAsync(string userId)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"EmptyCartAsync called with userId={userId}");
|
||||||
|
userCartItems[userId] = new Hipstershop.Cart();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Hipstershop.Cart> GetCartAsync(string userId)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"GetCartAsync called with userId={userId}");
|
||||||
|
Hipstershop.Cart cart = null;
|
||||||
if (!userCartItems.TryGetValue(userId, out cart))
|
if (!userCartItems.TryGetValue(userId, out cart))
|
||||||
{
|
{
|
||||||
cart = new Cart(userId);
|
Console.WriteLine($"No carts for user {userId}");
|
||||||
}
|
}
|
||||||
else
|
return Task.FromResult(cart);
|
||||||
{
|
|
||||||
cart = userCartItems[userId];
|
|
||||||
}
|
|
||||||
cart.AddItem(productId, quantity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EmptyCart(string userId)
|
|
||||||
{
|
|
||||||
Cart cart;
|
|
||||||
if (userCartItems.TryGetValue(userId, out cart))
|
|
||||||
{
|
|
||||||
cart.EmptyCart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Cart GetCart(string userId)
|
|
||||||
{
|
|
||||||
Cart cart = null;
|
|
||||||
userCartItems.TryGetValue(userId, out cart);
|
|
||||||
return cart;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -29,6 +29,8 @@ namespace cartservice.cartstore
|
||||||
|
|
||||||
public async Task AddItemAsync(string userId, string productId, int quantity)
|
public async Task AddItemAsync(string userId, string productId, int quantity)
|
||||||
{
|
{
|
||||||
|
Console.WriteLine($"AddItemAsync called with userId={userId}, productId={productId}, quantity={quantity}");
|
||||||
|
|
||||||
var db = redis.GetDatabase();
|
var db = redis.GetDatabase();
|
||||||
|
|
||||||
// Access the cart from the cache
|
// Access the cart from the cache
|
||||||
|
@ -44,11 +46,15 @@ namespace cartservice.cartstore
|
||||||
cart = Hipstershop.Cart.Parser.ParseFrom(value);
|
cart = Hipstershop.Cart.Parser.ParseFrom(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cart.UserId = userId;
|
||||||
cart.Items.Add(new Hipstershop.CartItem { ProductId = productId, Quantity = quantity });
|
cart.Items.Add(new Hipstershop.CartItem { ProductId = productId, Quantity = quantity });
|
||||||
|
await db.HashSetAsync(userId, new[]{ new HashEntry(CART_FIELD_NAME, cart.ToByteArray()) });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task EmptyCartAsync(string userId)
|
public async Task EmptyCartAsync(string userId)
|
||||||
{
|
{
|
||||||
|
Console.WriteLine($"EmptyCartAsync called with userId={userId}");
|
||||||
|
|
||||||
var db = redis.GetDatabase();
|
var db = redis.GetDatabase();
|
||||||
|
|
||||||
// Update the cache with empty cart for given user
|
// Update the cache with empty cart for given user
|
||||||
|
@ -57,6 +63,7 @@ namespace cartservice.cartstore
|
||||||
|
|
||||||
public async Task<Hipstershop.Cart> GetCartAsync(string userId)
|
public async Task<Hipstershop.Cart> GetCartAsync(string userId)
|
||||||
{
|
{
|
||||||
|
Console.WriteLine($"GetCartAsync called with userId={userId}");
|
||||||
var db = redis.GetDatabase();
|
var db = redis.GetDatabase();
|
||||||
|
|
||||||
// Access the cart from the cache
|
// Access the cart from the cache
|
||||||
|
|
|
@ -8,6 +8,9 @@ set CART_SERVICE_PORT=7070
|
||||||
rem run docker container with redis
|
rem run docker container with redis
|
||||||
rem docker run -d --name=redis -p %REDIS_PORT%:%REDIS_PORT% redis:alpine
|
rem docker run -d --name=redis -p %REDIS_PORT%:%REDIS_PORT% redis:alpine
|
||||||
|
|
||||||
|
rem running locally
|
||||||
|
dotnet build ..\.
|
||||||
|
dotnet run --project ../cartservice.csproj start
|
||||||
rem run docker container with cart service
|
rem run docker container with cart service
|
||||||
docker run -it --rm -e REDIS_ADDR=%REDIS_ADDR%:%REDIS_PORT% -e CART_SERVICE_ADDR=%CART_SERVICE_ADDR% -e CART_SERVICE_PORT=%CART_SERVICE_PORT% -p %CART_SERVICE_PORT%:%CART_SERVICE_PORT% cartservice
|
rem docker run -it --rm -e REDIS_ADDR=%REDIS_ADDR%:%REDIS_PORT% -e CART_SERVICE_ADDR=%CART_SERVICE_ADDR% -e CART_SERVICE_PORT=%CART_SERVICE_PORT% -p %CART_SERVICE_PORT%:%CART_SERVICE_PORT% cartservice
|
||||||
rem -e GRPC_TRACE=all -e GRPC_VERBOSITY=debug
|
rem -e GRPC_TRACE=all -e GRPC_VERBOSITY=debug
|
|
@ -9,19 +9,20 @@ namespace cartservice
|
||||||
{
|
{
|
||||||
public class E2ETests
|
public class E2ETests
|
||||||
{
|
{
|
||||||
private static string serverHostName = "172.17.0.2";
|
private static string serverHostName = "localhost";
|
||||||
private static int port = 7070;
|
private static int port = 7070;
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task AddItem_ItemInserted()
|
public async Task AddItem_ItemInserted()
|
||||||
{
|
{
|
||||||
string userId = "user1";
|
string userId = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
// Construct server's Uri
|
// Construct server's Uri
|
||||||
string targetUri = $"{serverHostName}:{port}";
|
string targetUri = $"{serverHostName}:{port}";
|
||||||
|
|
||||||
// Create a GRPC communication channel between the client and the server
|
// Create a GRPC communication channel between the client and the server
|
||||||
var channel = new Channel(targetUri, ChannelCredentials.Insecure);
|
var channel = new Channel(targetUri, ChannelCredentials.Insecure);
|
||||||
|
|
||||||
//ar interceptorObject = new ObjecT();
|
//ar interceptorObject = new ObjecT();
|
||||||
//var channel.Intercept(interceptorObject);
|
//var channel.Intercept(interceptorObject);
|
||||||
// Create a proxy object to work with the server
|
// Create a proxy object to work with the server
|
||||||
|
@ -37,6 +38,7 @@ namespace cartservice
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
for (int i = 0; i < 3; i++)
|
for (int i = 0; i < 3; i++)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -47,18 +49,27 @@ namespace cartservice
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
await Task.Delay(1000);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
var getCardRequest = new GetCartRequest
|
await client.AddItemAsync(request);
|
||||||
|
var getCartRequest = new GetCartRequest
|
||||||
{
|
{
|
||||||
UserId = userId
|
UserId = userId
|
||||||
};
|
};
|
||||||
var cart = await client.GetCartAsync(getCardRequest);
|
//await client.EmptyCartAsync(nameof)
|
||||||
|
//await client.EmptyCartAsync(new EmptyCartRequest{ UserId = userId });
|
||||||
|
|
||||||
|
var cart = await client.GetCartAsync(getCartRequest);
|
||||||
|
Assert.NotNull(cart);
|
||||||
Assert.Equal(userId, cart.UserId);
|
Assert.Equal(userId, cart.UserId);
|
||||||
Assert.Single(cart.Items);
|
Assert.Single(cart.Items);
|
||||||
|
|
||||||
|
await client.EmptyCartAsync(new EmptyCartRequest{ UserId = userId });
|
||||||
|
cart = await client.GetCartAsync(getCartRequest);
|
||||||
|
Assert.Empty(cart.Items);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue