Fixing cache implementation

This commit is contained in:
Simon Zeltser 2018-06-25 09:44:56 -07:00
parent 3262ff82b0
commit c3c76effea
8 changed files with 99 additions and 86 deletions

13
.vscode/launch.json vendored
View file

@ -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
View file

@ -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"
} }
] ]

View file

@ -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;
}
} }
} }
} }

View file

@ -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)

View file

@ -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;
} }
} }
} }

View file

@ -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

View file

@ -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

View file

@ -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);
} }
} }
} }