Skip to content

Commit 7c0e2a1

Browse files
committed
Catalog.API gRPC support for one endpoint
1 parent e1ace6f commit 7c0e2a1

16 files changed

Lines changed: 332 additions & 53 deletions

docker-compose.override.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ services:
7777
catalog.api:
7878
environment:
7979
- ASPNETCORE_ENVIRONMENT=Development
80-
- ASPNETCORE_URLS=http://0.0.0.0:80
80+
- ASPNETCORE_URLS=http://0.0.0.0:80;https://0.0.0.0:443;
8181
- ConnectionString=${ESHOP_AZURE_CATALOG_DB:-Server=sql.data;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word}
8282
- PicBaseUrl=${ESHOP_AZURE_STORAGE_CATALOG_URL:-http://localhost:5202/api/v1/c/catalog/items/[0]/pic/} #Local: You need to open your local dev-machine firewall at range 5100-5110.
8383
- EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq}
@@ -91,8 +91,8 @@ services:
9191
- ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY}
9292
- OrchestratorType=${ORCHESTRATOR_TYPE}
9393
ports:
94-
- "5101:80" # Important: In a production environment your should remove the external port (5101) kept here for microservice debugging purposes.
95-
# The API Gateway redirects and access through the internal port (80).
94+
- "5101:80"
95+
- "9101:443"
9696

9797
ordering.api:
9898
environment:

src/Services/Catalog/Catalog.API/Catalog.API.csproj

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22

33
<PropertyGroup>
4-
<TargetFramework>netcoreapp2.2</TargetFramework>
4+
<TargetFramework>netcoreapp3.0</TargetFramework>
55
<DebugType>portable</DebugType>
66
<PreserveCompilationContext>true</PreserveCompilationContext>
77
<AssemblyName>Catalog.API</AssemblyName>
@@ -32,8 +32,18 @@
3232
</Content>
3333
</ItemGroup>
3434

35+
<ItemGroup>
36+
<Protobuf Include="Proto\catalog.proto" GrpcServices="Server" Generator="MSBuild:Compile" />
37+
<Content Include="@(Protobuf)" />
38+
<None Remove="@(Protobuf)" />
39+
</ItemGroup>
40+
3541

3642
<ItemGroup>
43+
<PackageReference Include="Grpc.AspNetCore.Server" Version="0.1.21-pre1" />
44+
<PackageReference Include="Google.Protobuf" Version="3.8.0" />
45+
<PackageReference Include="Grpc.Tools" Version="1.21.0" PrivateAssets="All" />
46+
3747
<PackageReference Include="AspNetCore.HealthChecks.AzureServiceBus" Version="2.2.0" />
3848
<PackageReference Include="AspNetCore.HealthChecks.AzureStorage" Version="2.2.0" />
3949
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="2.2.0" />
@@ -43,18 +53,16 @@
4353
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.2.1" />
4454
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.6.1" />
4555
<PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="1.0.2" />
46-
<PackageReference Include="Microsoft.ApplicationInsights.ServiceFabric" Version="2.2.2" />
47-
<PackageReference Include="Microsoft.AspNetCore.App" />
4856
<PackageReference Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" />
4957
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="2.2.0" />
50-
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.2.0" />
51-
<PackageReference Include="Serilog.AspNetCore" Version="2.1.1" />
58+
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="3.0.0-preview6.19304.6" />
59+
<PackageReference Include="Serilog.AspNetCore" Version="3.0.0-dev-00053" />
5260
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.1.3" />
53-
<PackageReference Include="Serilog.Settings.Configuration" Version="3.0.1" />
61+
<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.1-dev-00209" />
5462
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
5563
<PackageReference Include="Serilog.Sinks.Seq" Version="4.0.0" />
5664
<PackageReference Include="Serilog.Sinks.Http" Version="4.2.1" />
57-
<PackageReference Include="Swashbuckle.AspNetCore" Version="3.0.0" />
65+
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0-rc2" />
5866
<PackageReference Include="System.IO.Compression.ZipFile" Version="4.3.0" />
5967
</ItemGroup>
6068

@@ -63,7 +71,6 @@
6371
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusServiceBus\EventBusServiceBus.csproj" />
6472
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" />
6573
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\IntegrationEventLogEF\IntegrationEventLogEF.csproj" />
66-
<ProjectReference Include="..\..\..\BuildingBlocks\WebHostCustomization\WebHost.Customization\WebHost.Customization.csproj" />
6774
</ItemGroup>
6875

6976
<ItemGroup>

src/Services/Catalog/Catalog.API/Controllers/PicController.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
1313
[ApiController]
1414
public class PicController : ControllerBase
1515
{
16-
private readonly IHostingEnvironment _env;
16+
private readonly IWebHostEnvironment _env;
1717
private readonly CatalogContext _catalogContext;
1818

19-
public PicController(IHostingEnvironment env,
19+
public PicController(IWebHostEnvironment env,
2020
CatalogContext catalogContext)
2121
{
2222
_env = env;

src/Services/Catalog/Catalog.API/Dockerfile

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS base
1+
FROM mcr.microsoft.com/dotnet/core/aspnet:3.0-buster-slim AS base
22
WORKDIR /app
33
EXPOSE 80
4+
EXPOSE 443
45

5-
FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build
6+
FROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster AS build
67
WORKDIR /src
78

89
COPY scripts scripts/
@@ -28,4 +29,6 @@ FROM build AS publish
2829
FROM base AS final
2930
WORKDIR /app
3031
COPY --from=publish /app .
32+
COPY --from=build /src/src/Services/Catalog/Catalog.API/Proto /app/Proto
33+
COPY --from=build /src/src/Services/Catalog/Catalog.API/eshop.pfx .
3134
ENTRYPOINT ["dotnet", "Catalog.API.dll"]
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using Microsoft.EntityFrameworkCore;
2+
using Microsoft.Extensions.Configuration;
3+
using Microsoft.Extensions.Hosting;
4+
using System;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Threading.Tasks;
9+
using Microsoft.Extensions.Logging;
10+
using Polly;
11+
using System.Data.SqlClient;
12+
13+
namespace Catalog.API.Extensions
14+
{
15+
public static class HostExtensions
16+
{
17+
public static bool IsInKubernetes(this IHost host)
18+
{
19+
var cfg = host.Services.GetService<IConfiguration>();
20+
var orchestratorType = cfg.GetValue<string>("OrchestratorType");
21+
return orchestratorType?.ToUpper() == "K8S";
22+
}
23+
24+
public static IHost MigrateDbContext<TContext>(this IHost host, Action<TContext, IServiceProvider> seeder) where TContext : DbContext
25+
{
26+
var underK8s = host.IsInKubernetes();
27+
28+
using (var scope = host.Services.CreateScope())
29+
{
30+
var services = scope.ServiceProvider;
31+
32+
var logger = services.GetRequiredService<ILogger<TContext>>();
33+
34+
var context = services.GetService<TContext>();
35+
36+
try
37+
{
38+
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
39+
40+
if (underK8s)
41+
{
42+
InvokeSeeder(seeder, context, services);
43+
}
44+
else
45+
{
46+
var retry = Policy.Handle<SqlException>()
47+
.WaitAndRetry(new TimeSpan[]
48+
{
49+
TimeSpan.FromSeconds(3),
50+
TimeSpan.FromSeconds(5),
51+
TimeSpan.FromSeconds(8),
52+
});
53+
54+
//if the sql server container is not created on run docker compose this
55+
//migration can't fail for network related exception. The retry options for DbContext only
56+
//apply to transient exceptions
57+
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
58+
retry.Execute(() => InvokeSeeder(seeder, context, services));
59+
}
60+
61+
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
62+
}
63+
catch (Exception ex)
64+
{
65+
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
66+
if (underK8s)
67+
{
68+
throw; // Rethrow under k8s because we rely on k8s to re-run the pod
69+
}
70+
}
71+
}
72+
73+
return host;
74+
}
75+
76+
private static void InvokeSeeder<TContext>(Action<TContext, IServiceProvider> seeder, TContext context, IServiceProvider services)
77+
where TContext : DbContext
78+
{
79+
context.Database.Migrate();
80+
seeder(context, services);
81+
}
82+
}
83+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using CatalogApi;
6+
using Grpc.Core;
7+
using Microsoft.EntityFrameworkCore;
8+
using Microsoft.eShopOnContainers.Services.Catalog.API;
9+
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
10+
using Microsoft.eShopOnContainers.Services.Catalog.API.Model;
11+
using Microsoft.Extensions.Options;
12+
using static CatalogApi.Catalog;
13+
14+
namespace Catalog.API.Grpc
15+
{
16+
public class CatalogService : CatalogBase
17+
{
18+
private readonly CatalogContext _catalogContext;
19+
private readonly CatalogSettings _settings;
20+
public CatalogService(CatalogContext dbContext, IOptions<CatalogSettings> settings)
21+
{
22+
_settings = settings.Value;
23+
_catalogContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
24+
}
25+
26+
public override async Task<CatalogItemResponse> GetItemById(CatalogItemRequest request, ServerCallContext context)
27+
{
28+
29+
if (request.Id <=0)
30+
{
31+
context.Status = new Status(StatusCode.FailedPrecondition, $"Id must be > 0 (received {request.Id})");
32+
return null;
33+
}
34+
35+
var item = await _catalogContext.CatalogItems.SingleOrDefaultAsync(ci => ci.Id == request.Id);
36+
var baseUri = _settings.PicBaseUrl;
37+
var azureStorageEnabled = _settings.AzureStorageEnabled;
38+
item.FillProductUrl(baseUri, azureStorageEnabled: azureStorageEnabled);
39+
40+
if (item != null)
41+
{
42+
return new CatalogItemResponse()
43+
{
44+
AvailableStock = item.AvailableStock,
45+
Description = item.Description,
46+
Id = item.Id,
47+
MaxStockThreshold = item.MaxStockThreshold,
48+
Name = item.Name,
49+
OnReorder = item.OnReorder,
50+
PictureFileName = item.PictureFileName,
51+
PictureUri = item.PictureUri,
52+
Price = (double)item.Price,
53+
RestockThreshold = item.RestockThreshold
54+
};
55+
}
56+
57+
context.Status = new Status(StatusCode.NotFound, $"Product with id {request.Id} do not exist");
58+
return null;
59+
}
60+
}
61+
}

src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
public class CatalogContextSeed
2020
{
21-
public async Task SeedAsync(CatalogContext context,IHostingEnvironment env,IOptions<CatalogSettings> settings,ILogger<CatalogContextSeed> logger)
21+
public async Task SeedAsync(CatalogContext context,IWebHostEnvironment env,IOptions<CatalogSettings> settings,ILogger<CatalogContextSeed> logger)
2222
{
2323
var policy = CreatePolicy(logger, nameof(CatalogContextSeed));
2424

src/Services/Catalog/Catalog.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@
44
using Microsoft.AspNetCore.Http;
55
using Microsoft.AspNetCore.Mvc;
66
using Microsoft.AspNetCore.Mvc.Filters;
7+
using Microsoft.Extensions.Hosting;
78
using Microsoft.Extensions.Logging;
89
using System.Net;
910

1011
namespace Catalog.API.Infrastructure.Filters
1112
{
1213
public class HttpGlobalExceptionFilter : IExceptionFilter
1314
{
14-
private readonly IHostingEnvironment env;
15+
private readonly IWebHostEnvironment env;
1516
private readonly ILogger<HttpGlobalExceptionFilter> logger;
1617

17-
public HttpGlobalExceptionFilter(IHostingEnvironment env, ILogger<HttpGlobalExceptionFilter> logger)
18+
public HttpGlobalExceptionFilter(IWebHostEnvironment env, ILogger<HttpGlobalExceptionFilter> logger)
1819
{
1920
this.env = env;
2021
this.logger = logger;

0 commit comments

Comments
 (0)