Merge branch 'master' of sso://user/ahmetb/microservices-demo

This commit is contained in:
Ahmet Alp Balkan 2018-06-21 11:45:06 -07:00
commit 95ed2f3b64
10 changed files with 237 additions and 53 deletions

28
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,28 @@
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/src/cartservice/bin/Debug/netcoreapp2.0/cartservice.dll",
"args": [],
"cwd": "${workspaceFolder}/src/cartservice",
// For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
"console": "internalConsole",
"stopAtEntry": false,
"internalConsoleOptions": "openOnSessionStart"
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
}
,]
}

15
.vscode/tasks.json vendored Normal file
View file

@ -0,0 +1,15 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/src/cartservice/cartservice.csproj"
],
"problemMatcher": "$msCompile"
}
]
}

3
src/cartservice/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/bin/*
/obj/*
.vs/*.*

View file

@ -2,6 +2,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using cartservice.interfaces;
using Grpc.Core; using Grpc.Core;
using Hipstershop; using Hipstershop;
using static Hipstershop.CartService; using static Hipstershop.CartService;
@ -11,65 +12,29 @@ namespace cartservice
// Cart wrapper to deal with grpc communication // Cart wrapper to deal with grpc communication
internal class CartServiceImpl : CartServiceBase internal class CartServiceImpl : CartServiceBase
{ {
private CartStore cartStore; private ICartStore cartStore;
private readonly static Empty Empty = new Empty();
public CartServiceImpl(CartStore cartStore) public CartServiceImpl(ICartStore cartStore)
{ {
this.cartStore = cartStore; this.cartStore = cartStore;
} }
public override Task<Empty> AddItem(AddItemRequest request, Grpc.Core.ServerCallContext context) public async override Task<Empty> AddItem(AddItemRequest request, Grpc.Core.ServerCallContext context)
{ {
cartStore.AddItem(request.UserId, request.Item.ProductId, request.Item.Quantity); await cartStore.AddItemAsync(request.UserId, request.Item.ProductId, request.Item.Quantity);
return Task.FromResult(new Empty()); return Empty;
} }
public override Task<Empty> EmptyCart(EmptyCartRequest request, ServerCallContext context) public async override Task<Empty> EmptyCart(EmptyCartRequest request, ServerCallContext context)
{ {
cartStore.EmptyCart(request.UserId); await cartStore.EmptyCartAsync(request.UserId);
return Task.FromResult(new Empty()); return Empty;
} }
public override Task<Hipstershop.Cart> GetCart(GetCartRequest request, ServerCallContext context) public async override Task<Hipstershop.Cart> GetCart(GetCartRequest request, ServerCallContext context)
{ {
var cart = cartStore.GetCart(request.UserId); return await cartStore.GetCartAsync(request.UserId);
return Task.FromResult(cart.ToHipsterCart());
}
}
internal class CartStore
{
// Maps between user and their cart
private ConcurrentDictionary<string, Cart> userCartItems = new ConcurrentDictionary<string, Cart>();
public void AddItem(string userId, string productId, int quantity)
{
Cart cart;
if (!userCartItems.TryGetValue(userId, out cart))
{
cart = new Cart(userId);
}
else
{
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

@ -1,29 +1,41 @@
using System; using System;
using System.IO;
using cartservice.cartstore;
using CommandLine; using CommandLine;
using Grpc.Core; using Grpc.Core;
using Microsoft.Extensions.Configuration;
namespace cartservice namespace cartservice
{ {
class Program class Program
{ {
const string CART_SERVICE_ADDRESS = "CART_SERVICE_ADDR";
const string REDIS_ADDRESS = "REDIS_ADDR";
[Verb("start", HelpText = "Starts the server listening on provided port")] [Verb("start", HelpText = "Starts the server listening on provided port")]
class ServerOptions class ServerOptions
{ {
[Option('h', "hostname", HelpText = "The ip on which the server is running. If not provided, CART_SERVICE_ADDR environment variable value will be used. If not defined, localhost is used")]
public string Host { get; set; }
[Option('p', "port", HelpText = "The port on for running the server", Required = true)] [Option('p', "port", HelpText = "The port on for running the server", Required = true)]
public int Port { get; set; } public int Port { get; set; }
[Option('r', "redis", HelpText = "The ip of redis cache")]
public string Redis { get; set; }
} }
static object StartServer(string host, int port) static object StartServer(string host, int port, string redisAddress)
{ {
var store = new CartStore(); var store = new RedisCartStore(redisAddress);
Server server = new Server Server server = new Server
{ {
Services = { Hipstershop.CartService.BindService(new CartServiceImpl(store)) }, Services = { Hipstershop.CartService.BindService(new CartServiceImpl(store)) },
Ports = { new ServerPort(host, port, ServerCredentials.Insecure) } Ports = { new ServerPort(host, port, ServerCredentials.Insecure) }
}; };
Console.WriteLine("Cart server is listening on port " + port); Console.WriteLine($"Cart server is listening at {host}:{port}");
Console.WriteLine("Press any key to stop the server..."); Console.WriteLine("Press any key to stop the server...");
server.Start(); server.Start();
@ -33,6 +45,7 @@ namespace cartservice
return null; return null;
} }
static void Main(string[] args) static void Main(string[] args)
{ {
if (args.Length == 0) if (args.Length == 0)
@ -45,15 +58,34 @@ namespace cartservice
{ {
case "start": case "start":
Parser.Default.ParseArguments<ServerOptions>(args).MapResult( Parser.Default.ParseArguments<ServerOptions>(args).MapResult(
(ServerOptions options) => StartServer("localhost", options.Port), (ServerOptions options) =>
{
string host = options.Host;
if (string.IsNullOrEmpty(host))
{
Console.WriteLine($"Reading host address from {CART_SERVICE_ADDRESS} environment variable...");
host = Environment.GetEnvironmentVariable(CART_SERVICE_ADDRESS);
if (string.IsNullOrEmpty(host))
{
Console.WriteLine("Setting the host to 127.0.0.1");
host = "127.0.0.1";
}
}
string redis = options.Redis;
if (string.IsNullOrEmpty(redis))
{
Console.WriteLine("Reading redis cache address from environment variable");
redis = Environment.GetEnvironmentVariable(REDIS_ADDRESS);
}
return StartServer(host, options.Port, redis);
},
errs => 1); errs => 1);
break; break;
default: default:
Console.WriteLine("Invalid command"); Console.WriteLine("Invalid command");
break; break;
} }
Console.WriteLine("Hello World!");
} }
} }
} }

View file

@ -11,6 +11,9 @@
<PackageReference Include="Google.Protobuf.Tools" Version="3.5.1" /> <PackageReference Include="Google.Protobuf.Tools" Version="3.5.1" />
<PackageReference Include="grpc" Version="1.12.0" /> <PackageReference Include="grpc" Version="1.12.0" />
<PackageReference Include="grpc.tools" Version="1.12.0" /> <PackageReference Include="grpc.tools" Version="1.12.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" />
<PackageReference Include="StackExchange.Redis" Version="1.2.6" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -0,0 +1,41 @@
using System.Collections.Concurrent;
using cartservice.interfaces;
namespace cartservice.cartstore
{
internal class LocalCartStore
{
// Maps between user and their cart
private ConcurrentDictionary<string, Cart> userCartItems = new ConcurrentDictionary<string, Cart>();
public void AddItem(string userId, string productId, int quantity)
{
Cart cart;
if (!userCartItems.TryGetValue(userId, out cart))
{
cart = new Cart(userId);
}
else
{
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

@ -0,0 +1,74 @@
using System;
using System.IO;
using System.Threading.Tasks;
using cartservice.interfaces;
using Google.Protobuf;
using Hipstershop;
using StackExchange.Redis;
namespace cartservice.cartstore
{
public class RedisCartStore : ICartStore
{
private const string CART_FIELD_NAME = "cart";
private readonly ConnectionMultiplexer redis;
private readonly byte[] emptyCartBytes;
public RedisCartStore(string redisAddress)
{
// Serialize empty cart into byte array.
var cart = new Hipstershop.Cart();
emptyCartBytes = cart.ToByteArray();
string connectionString = $"{redisAddress},ssl=false,allowAdmin=true";
Console.WriteLine("Connecting to Redis: " + connectionString);
redis = ConnectionMultiplexer.Connect(connectionString);
}
public async Task AddItemAsync(string userId, string productId, int quantity)
{
var db = redis.GetDatabase();
// Access the cart from the cache
var value = await db.HashGetAsync(userId, CART_FIELD_NAME);
Hipstershop.Cart cart;
if (value.IsNull)
{
cart = new Hipstershop.Cart();
}
else
{
cart = Hipstershop.Cart.Parser.ParseFrom(value);
}
cart.Items.Add(new Hipstershop.CartItem { ProductId = productId, Quantity = quantity });
}
public async Task EmptyCartAsync(string userId)
{
var db = redis.GetDatabase();
// Update the cache with empty cart for given user
await db.HashSetAsync(userId, new[] { new HashEntry(CART_FIELD_NAME, emptyCartBytes) });
}
public async Task<Hipstershop.Cart> GetCartAsync(string userId)
{
var db = redis.GetDatabase();
// Access the cart from the cache
var value = await db.HashGetAsync(userId, CART_FIELD_NAME);
Hipstershop.Cart cart = null;
if (!value.IsNull)
{
cart = Hipstershop.Cart.Parser.ParseFrom(value);
}
return cart;
}
}
}

View file

@ -0,0 +1,12 @@
using System.Threading.Tasks;
namespace cartservice.interfaces
{
internal interface ICartStore
{
Task AddItemAsync(string userId, string productId, int quantity);
Task EmptyCartAsync(string userId);
Task<Hipstershop.Cart> GetCartAsync(string userId);
}
}

View file

@ -0,0 +1,11 @@
@echo off
rem install redis on windows using choco
rem choco install redis-64
rem run redis
redis-server --daemonize yes
rem testing locally
rem redis-cli
rem SET foo bar
rem GET foo