From fd3d5937f84fa29a9e1f23c92f6fc6335c593b33 Mon Sep 17 00:00:00 2001 From: Cesar De la Torre Date: Tue, 11 Oct 2016 10:31:23 -0700 Subject: [PATCH 1/2] Minor change in dependencies in docker-compose.yml --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index f78a08fdb..ff315880a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,7 @@ services: - "80:80" depends_on: - catalog.api + - ordering.api catalog.api: image: eshop/catalog.api:latest From 2663ac78771aa4f86ab9bbaa6462915e294a3034 Mon Sep 17 00:00:00 2001 From: glennc Date: Mon, 17 Oct 2016 20:10:18 -0700 Subject: [PATCH 2/2] initial basket example. --- build-images.ps1 | 15 ++++- build-images.sh | 4 +- docker-compose.yml | 12 +++- src/Services/Basket/Basket.API/.dockerignore | 2 + .../Basket/Basket.API/BasketSettings.cs | 12 ++++ .../Controllers/BasketController.cs | 44 +++++++++++++ .../Controllers/ValuesController.cs | 44 ------------- src/Services/Basket/Basket.API/Dockerfile | 6 ++ .../Basket/Basket.API/Model/Basket.cs | 18 +++++ .../Basket/Basket.API/Model/BasketItem.cs | 14 ++++ .../Basket.API/Model/IBasketRepository.cs | 14 ++++ .../Basket.API/Model/RedisBasketRepository.cs | 66 +++++++++++++++++++ .../Basket.API/Properties/launchSettings.json | 4 ++ src/Services/Basket/Basket.API/Startup.cs | 19 ++++++ .../Basket/Basket.API/appsettings.json | 3 +- .../Basket.API/docker-compose.dev.debug.yml | 17 +++++ .../Basket.API/docker-compose.dev.release.yml | 9 +++ .../Basket/Basket.API/docker-compose.yml | 16 +++++ src/Services/Basket/Basket.API/project.json | 27 ++++---- 19 files changed, 285 insertions(+), 61 deletions(-) create mode 100644 src/Services/Basket/Basket.API/.dockerignore create mode 100644 src/Services/Basket/Basket.API/BasketSettings.cs create mode 100644 src/Services/Basket/Basket.API/Controllers/BasketController.cs delete mode 100644 src/Services/Basket/Basket.API/Controllers/ValuesController.cs create mode 100644 src/Services/Basket/Basket.API/Dockerfile create mode 100644 src/Services/Basket/Basket.API/Model/Basket.cs create mode 100644 src/Services/Basket/Basket.API/Model/BasketItem.cs create mode 100644 src/Services/Basket/Basket.API/Model/IBasketRepository.cs create mode 100644 src/Services/Basket/Basket.API/Model/RedisBasketRepository.cs create mode 100644 src/Services/Basket/Basket.API/docker-compose.dev.debug.yml create mode 100644 src/Services/Basket/Basket.API/docker-compose.dev.release.yml create mode 100644 src/Services/Basket/Basket.API/docker-compose.yml diff --git a/build-images.ps1 b/build-images.ps1 index 8e1fbe1af..203df06ee 100644 --- a/build-images.ps1 +++ b/build-images.ps1 @@ -39,8 +39,19 @@ Write-Host "Restore Dependencies just in case as it is needed to run dotnet publ dotnet restore $orderingPathToJson dotnet build $orderingPathToJson dotnet publish $orderingPathToJson -o $orderingPathToPub - + +#*** Basket service image *** +$basketPathToJson = $scriptPath + "\src\Services\Basket\Basket.API\project.json" +Write-Host "basketPathToJson is $orderingPathToJson" -ForegroundColor Yellow +$basketPathToPub = $scriptPath + "\pub\basket" +Write-Host "basketPathToPub is $basketPathToPub" -ForegroundColor Yellow + +Write-Host "Restore Dependencies just in case as it is needed to run dotnet publish" -ForegroundColor Blue +dotnet restore $basketPathToJson +dotnet build $basketPathToPub +dotnet publish $basketPathToJson -o $basketPathToPub docker build -t eshop/web $webPathToPub docker build -t eshop/catalog.api $catalogPathToPub -docker build -t eshop/ordering.api $orderingPathToPub \ No newline at end of file +docker build -t eshop/ordering.api $orderingPathToPub +docker build -t eshop/basket.api $basketPathToPub \ No newline at end of file diff --git a/build-images.sh b/build-images.sh index d17527c8d..4e86be0e9 100644 --- a/build-images.sh +++ b/build-images.sh @@ -4,7 +4,9 @@ rm -rf ./pub dotnet publish "$(pwd)/src/Web/WebMVC/project.json" -o "$(pwd)/pub/webMVC" dotnet publish "$(pwd)/src/Services/Catalog/Catalog.API/project.json" -o "$(pwd)/pub/catalog" dotnet publish "$(pwd)/src/Services/Ordering/Ordering.API/project.json" -o "$(pwd)/pub/ordering" +dotnet publish "$(pwd)/src/Services/Basket/Basket.API/project.json" -o "$(pwd)/pub/basket" docker build -t eshop/web "$(pwd)/pub/webMVC" docker build -t eshop/catalog.api "$(pwd)/pub/catalog" -docker build -t eshop/ordering.api "$(pwd)/pub/ordering" \ No newline at end of file +docker build -t eshop/ordering.api "$(pwd)/pub/ordering" +docker build -t eshop/basket.api "$(pwd)/pub/basket" \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index ff315880a..ca88c4320 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -42,4 +42,14 @@ services: ordering.data: image: eshop/ordering.data.sqlserver.linux ports: - - "1433:1433" \ No newline at end of file + - "1433:1433" + + basket.api: + image: eshop/basket.api:latest + environment: + - ConnectionString=basket.data + depends_on: + - basket.data + + basket.data: + image: redis \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/.dockerignore b/src/Services/Basket/Basket.API/.dockerignore new file mode 100644 index 000000000..63d337d41 --- /dev/null +++ b/src/Services/Basket/Basket.API/.dockerignore @@ -0,0 +1,2 @@ +docker-compose.yml +Dockerfile diff --git a/src/Services/Basket/Basket.API/BasketSettings.cs b/src/Services/Basket/Basket.API/BasketSettings.cs new file mode 100644 index 000000000..d584c102f --- /dev/null +++ b/src/Services/Basket/Basket.API/BasketSettings.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.Services.Basket.API +{ + public class BasketSettings + { + public string ConnectionString { get; set; } + } +} diff --git a/src/Services/Basket/Basket.API/Controllers/BasketController.cs b/src/Services/Basket/Basket.API/Controllers/BasketController.cs new file mode 100644 index 000000000..456000658 --- /dev/null +++ b/src/Services/Basket/Basket.API/Controllers/BasketController.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.eShopOnContainers.Services.Basket.API.Model; + +namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers +{ + //NOTE: Right now this is a very chunky API, as the app evolves it is possible we would + //want to make the actions more fine graned, add basket item as an action for example. + //If this is the case we should also investigate changing the serialization format used for Redis, + //using a HashSet instead of a simple string. + [Route("/")] + public class BasketController : Controller + { + private IBasketRepository _repository; + + public BasketController(IBasketRepository repository) + { + _repository = repository; + } + // GET api/values/5 + [HttpGet("{id}")] + public async Task Get(Guid id) + { + return await _repository.GetBasket(id); + } + + // POST api/values + [HttpPost] + public void Post([FromBody]CustomerBasket value) + { + _repository.UpdateBasket(value); + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(Guid id) + { + _repository.DeleteBasket(id); + } + } +} diff --git a/src/Services/Basket/Basket.API/Controllers/ValuesController.cs b/src/Services/Basket/Basket.API/Controllers/ValuesController.cs deleted file mode 100644 index 689d9a37a..000000000 --- a/src/Services/Basket/Basket.API/Controllers/ValuesController.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; - -namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers -{ - [Route("api/[controller]")] - public class ValuesController : Controller - { - // GET api/values - [HttpGet] - public IEnumerable Get() - { - return new string[] { "value1", "value2" }; - } - - // GET api/values/5 - [HttpGet("{id}")] - public string Get(int id) - { - return "value"; - } - - // POST api/values - [HttpPost] - public void Post([FromBody]string value) - { - } - - // PUT api/values/5 - [HttpPut("{id}")] - public void Put(int id, [FromBody]string value) - { - } - - // DELETE api/values/5 - [HttpDelete("{id}")] - public void Delete(int id) - { - } - } -} diff --git a/src/Services/Basket/Basket.API/Dockerfile b/src/Services/Basket/Basket.API/Dockerfile new file mode 100644 index 000000000..b0801c2b0 --- /dev/null +++ b/src/Services/Basket/Basket.API/Dockerfile @@ -0,0 +1,6 @@ +FROM microsoft/aspnetcore:1.0.1 +ENTRYPOINT ["dotnet", "Basket.API.dll"] +ARG source=. +WORKDIR /app +EXPOSE 80 +COPY $source . diff --git a/src/Services/Basket/Basket.API/Model/Basket.cs b/src/Services/Basket/Basket.API/Model/Basket.cs new file mode 100644 index 000000000..32c10b5b2 --- /dev/null +++ b/src/Services/Basket/Basket.API/Model/Basket.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.Services.Basket.API.Model +{ + public class CustomerBasket + { + public Guid CustomerId { get; private set; } + public IList BasketItems => new List(); + + public CustomerBasket(Guid customerId) + { + CustomerId = customerId; + } + } +} diff --git a/src/Services/Basket/Basket.API/Model/BasketItem.cs b/src/Services/Basket/Basket.API/Model/BasketItem.cs new file mode 100644 index 000000000..75e53a25e --- /dev/null +++ b/src/Services/Basket/Basket.API/Model/BasketItem.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.Services.Basket.API.Model +{ + public class BasketItem + { + public Guid Id { get; set; } + public decimal Price { get; set; } + public int Count { get; set; } + } +} diff --git a/src/Services/Basket/Basket.API/Model/IBasketRepository.cs b/src/Services/Basket/Basket.API/Model/IBasketRepository.cs new file mode 100644 index 000000000..4f422cf4d --- /dev/null +++ b/src/Services/Basket/Basket.API/Model/IBasketRepository.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.Services.Basket.API.Model +{ + public interface IBasketRepository + { + Task GetBasket(Guid customerId); + Task UpdateBasket(CustomerBasket basket); + Task DeleteBasket(Guid id); + } +} diff --git a/src/Services/Basket/Basket.API/Model/RedisBasketRepository.cs b/src/Services/Basket/Basket.API/Model/RedisBasketRepository.cs new file mode 100644 index 000000000..65a104511 --- /dev/null +++ b/src/Services/Basket/Basket.API/Model/RedisBasketRepository.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using StackExchange.Redis; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Microsoft.Extensions.Logging; +using System.Net; + +namespace Microsoft.eShopOnContainers.Services.Basket.API.Model +{ + public class RedisBasketRepository : IBasketRepository + { + private ILogger _logger; + private BasketSettings _settings; + + private ConnectionMultiplexer _redis; + + + public RedisBasketRepository(IOptions options, ILoggerFactory loggerFactory) + { + _settings = options.Value; + _logger = loggerFactory.CreateLogger(); + + } + + public async Task DeleteBasket(Guid id) + { + var database = await GetDatabase(); + return await database.KeyDeleteAsync(id.ToString()); + } + + public async Task GetBasket(Guid customerId) + { + var database = await GetDatabase(); + + var data = await database.StringGetAsync(customerId.ToString()); + if (data.IsNullOrEmpty) + { + return null; + } + + return JsonConvert.DeserializeObject(data); + } + + public async Task UpdateBasket(CustomerBasket basket) + { + var database = await GetDatabase(); + return await database.StringSetAsync(basket.CustomerId.ToString(), JsonConvert.SerializeObject(basket)); + } + + private async Task GetDatabase() + { + if (_redis == null) + { + //TODO: Need to make this more robust. Also want to understand why the static connection method cannot accept dns names. + var ips = await Dns.GetHostAddressesAsync(_settings.ConnectionString); + _logger.LogInformation($"Connecting to database {_settings.ConnectionString} at IP {ips.First().ToString()}"); + _redis = await ConnectionMultiplexer.ConnectAsync(ips.First().ToString()); + } + + return _redis.GetDatabase(); + } + } +} diff --git a/src/Services/Basket/Basket.API/Properties/launchSettings.json b/src/Services/Basket/Basket.API/Properties/launchSettings.json index 8ac2d090f..c734d837c 100644 --- a/src/Services/Basket/Basket.API/Properties/launchSettings.json +++ b/src/Services/Basket/Basket.API/Properties/launchSettings.json @@ -23,6 +23,10 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } + }, + "Docker": { + "launchBrowser": true, + "launchUrl": "http://localhost:{ServicePort}/api/values" } } } \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index 78941ddc0..54a1f2002 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -7,6 +7,10 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.eShopOnContainers.Services.Basket.API.Model; +using StackExchange.Redis; +using Microsoft.Extensions.Options; +using System.Net; namespace Microsoft.eShopOnContainers.Services.Basket.API { @@ -29,6 +33,21 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API { // Add framework services. services.AddMvc(); + services.Configure(Configuration); + + //By connection here we are making sure that our service + //cannot start until redis is ready. This might slow down startup, + //but given that it is there is a delay on resolving the ip address + //and then creating the connection it seems reasonable to move + //that cost to startup instead of having the first request pay the + //penalty. + services.AddSingleton((sp) => { + var config = sp.GetRequiredService>().Value; + var ips = Dns.GetHostAddressesAsync(config.ConnectionString).Result; + return ConnectionMultiplexer.Connect(ips.First().ToString()); + }); + + services.AddTransient(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Services/Basket/Basket.API/appsettings.json b/src/Services/Basket/Basket.API/appsettings.json index fa8ce71a9..b636bbaf6 100644 --- a/src/Services/Basket/Basket.API/appsettings.json +++ b/src/Services/Basket/Basket.API/appsettings.json @@ -6,5 +6,6 @@ "System": "Information", "Microsoft": "Information" } - } + }, + "ConnectionString": "127.0.0.1" } diff --git a/src/Services/Basket/Basket.API/docker-compose.dev.debug.yml b/src/Services/Basket/Basket.API/docker-compose.dev.debug.yml new file mode 100644 index 000000000..b2d89a04d --- /dev/null +++ b/src/Services/Basket/Basket.API/docker-compose.dev.debug.yml @@ -0,0 +1,17 @@ +version: '2' + +services: + basket.api: + build: + args: + source: obj/Docker/empty/ + labels: + - "com.microsoft.visualstudio.targetoperatingsystem=linux" + environment: + - ASPNETCORE_ENVIRONMENT=Development + - DOTNET_USE_POLLING_FILE_WATCHER=1 + volumes: + - .:/app + - ~/.nuget/packages:/root/.nuget/packages:ro + - ~/clrdbg:/clrdbg:ro + entrypoint: tail -f /dev/null diff --git a/src/Services/Basket/Basket.API/docker-compose.dev.release.yml b/src/Services/Basket/Basket.API/docker-compose.dev.release.yml new file mode 100644 index 000000000..259308345 --- /dev/null +++ b/src/Services/Basket/Basket.API/docker-compose.dev.release.yml @@ -0,0 +1,9 @@ +version: '2' + +services: + basket.api: + labels: + - "com.microsoft.visualstudio.targetoperatingsystem=linux" + volumes: + - ~/clrdbg:/clrdbg:ro + entrypoint: tail -f /dev/null diff --git a/src/Services/Basket/Basket.API/docker-compose.yml b/src/Services/Basket/Basket.API/docker-compose.yml new file mode 100644 index 000000000..71a8bd170 --- /dev/null +++ b/src/Services/Basket/Basket.API/docker-compose.yml @@ -0,0 +1,16 @@ +version: '2' + +services: + basket.api: + image: user/basket.api${TAG} + environment: + - ConnectionString=basket.data + build: + context: . + dockerfile: Dockerfile + ports: + - "32783:80" + depends_on: + - basket.data + basket.data: + image: redis diff --git a/src/Services/Basket/Basket.API/project.json b/src/Services/Basket/Basket.API/project.json index af6281beb..f73d18ba5 100644 --- a/src/Services/Basket/Basket.API/project.json +++ b/src/Services/Basket/Basket.API/project.json @@ -1,9 +1,10 @@ -{ +{ "dependencies": { "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, + "System.Threading": "4.0.11", "Microsoft.AspNetCore.Mvc": "1.0.0", "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", @@ -13,13 +14,13 @@ "Microsoft.Extensions.Logging": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Logging.Debug": "1.0.0", - "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0" + "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", + "StackExchange.Redis": "1.1.608", + "Newtonsoft.Json": "9.0.1" }, - "tools": { "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" }, - "frameworks": { "netcoreapp1.0": { "imports": [ @@ -28,29 +29,31 @@ ] } }, - "buildOptions": { "emitEntryPoint": true, - "preserveCompilationContext": true + "preserveCompilationContext": true, + "debugType": "portable" }, - "runtimeOptions": { "configProperties": { "System.GC.Server": true } }, - "publishOptions": { "include": [ "wwwroot", "Views", "Areas/**/Views", "appsettings.json", - "web.config" + "web.config", + "Dockerfile", + "docker-compose.yml", + ".dockerignore" ] }, - "scripts": { - "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] + "postpublish": [ + "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" + ] } -} +} \ No newline at end of file