A sample demonstrating how to implement a multitenant facility management and accommodation booking application as native Azure Service Fabric reliable services.
Switch branches/tags
Nothing to show
Clone or download
Latest commit 59628b4 Aug 2, 2018
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
BookFast.Booking.Client updated booking service client library Aug 2, 2018
BookFast.Booking.CommandStack enabled reliable events in booking service Aug 1, 2018
BookFast.Booking.Data updated booking service client library Aug 2, 2018
BookFast.Booking.Domain refactored booking service with cqrs Jul 31, 2018
BookFast.Booking.QueryStack refactored booking service with cqrs Jul 31, 2018
BookFast.Booking enabled reliable events in booking service Aug 1, 2018
BookFast.Facility.Client updated to service fabric 6.2 Jun 14, 2018
BookFast.Facility.CommandStack updated reliable events implementation Jun 26, 2018
BookFast.Facility.Data use dedicated data model for reliable events Aug 1, 2018
BookFast.Facility.Domain establish a security context when dispatching reliable events May 8, 2018
BookFast.Facility.QueryStack keep working on facility service reimplementation Mar 27, 2018
BookFast.Facility added default reliable event mapper implementation Jul 18, 2018
BookFast.Files.Business use integers as facility and accommodation identifiers throughout the… Apr 10, 2018
BookFast.Files.Client updated to service fabric 6.2 Jun 14, 2018
BookFast.Files.Contracts use integers as facility and accommodation identifiers throughout the… Apr 10, 2018
BookFast.Files.Data updated to service fabric 6.2 Jun 14, 2018
BookFast.Files introduced a centralized way to configure mvc services Jun 14, 2018
BookFast.Search.Adapter updated to service fabric 6.2 Jun 14, 2018
BookFast.Search.Client updated to service fabric 6.2 Jun 14, 2018
BookFast.Search.Contracts updated search indexer May 11, 2018
BookFast.Search.Indexer added default reliable event mapper implementation Jul 18, 2018
BookFast.Search introduced a centralized way to configure mvc services Jun 14, 2018
BookFast.Web.Contracts updated to service fabric 6.2 Jun 14, 2018
BookFast.Web.Proxy updated booking service client library Aug 2, 2018
BookFast.Web updated booking service client library Aug 2, 2018
BookFast use per service notificaiton queues Jul 18, 2018
Certificate added a test certificate Jun 16, 2017
Common do not fail operation when there was a failure sending event notifica… Aug 2, 2018
.gitignore added a test certificate Jun 16, 2017
BookFast.sln refactored booking service with cqrs Jul 31, 2018
BookFastServiceFabric.png updated readme Mar 24, 2017
LICENSE Initial commit Dec 19, 2016
README.md updated readme Aug 2, 2018

README.md

Book Fast (Service Fabric)

A sample demonstrating how to implement a multitenant facility management and accommodation booking application as native Azure Service Fabric reliable services.

Here's the introductory blog post.

Features

Architecture

  • 4 bounded contexts
  • CQRS and DDD (with reliable domain events)
  • Stateless and stateful services
  • ASP.NET Core 2.x Web API and web frontend

Service Fabric

Security

Azure services

  • Azure SQL databases
  • Azure Storage
  • Azure Service Bus
  • Azure Search

Misc

BookFast Service Fabric

Configuration

BookFast.sfproj references ..\..\..\config\BookFast\Local.xml that is used in local deployment profiles. This file is not included in the repository and you will need to provide your configuration overrides.

Here's a short description of configuration parameters:

<?xml version="1.0" encoding="utf-8"?>
<Application xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Name="fabric:/BookFast" xmlns="http://schemas.microsoft.com/2011/01/fabric">
  <Parameters>
    <Parameter Name="HttpsCertThumbprint" Value="Thumbprint of an SSL certificate installed in the local store" />
    
    <Parameter Name="ASPNETCORE_ENVIRONMENT" Value="Standard APS.NET Core environment setting, e.g. Development" />
    
    <Parameter Name="FacilityServiceUri" Value="http://localhost:19081/BookFast/FacilityService/" />
    <Parameter Name="FilesServiceUri" Value="fabric:/BookFast/FilesService" />
    <Parameter Name="SearchServiceUri" Value="fabric:/BookFast/SearchService" />
    <Parameter Name="BookingServiceUri" Value="fabric:/BookFast/BookingService" />

    <Parameter Name="Data:DefaultConnection:ConnectionString" Value="Connection string to a SQL database" />
    <Parameter Name="Data:Azure:Storage:ConnectionString" Value="Connection string to an Azure storage account" />
    <Parameter Name="Data:Azure:Storage:ImageContainer" Value="book-fast" />
    
    <Parameter Name="ServiceBus:ConnectionString" Value="Connection string to Service Bus topic" />
    <Parameter Name="ServiceBus:TopicName" Value="bookfast" />

    <Parameter Name="FacilityService:ServiceBus:NotificationQueueConnection" Value="Connection string to the notification queue" />
    <Parameter Name="FacilityService:ServiceBus:NotificationQueueName" Value="bookfast-facility-notifications" />

    <Parameter Name="BookingService:ServiceBus:NotificationQueueConnection" Value="Connection string to the notification queue" />
    <Parameter Name="BookingService:ServiceBus:NotificationQueueName" Value="bookfast-booking-notifications" />

    <Parameter Name="Search:QueryKey" Value="Azure Search query key" />
    <Parameter Name="Search:AdminKey" Value="Azure Search admin key" />
    <Parameter Name="Search:ServiceName" Value="Azure Search service name" />
    <Parameter Name="Search:IndexName" Value="book-fast" />
    
    <Parameter Name="ApplicationInsights:InstrumentationKey" Value="Application Insights resource instrumentation key" />

    <Parameter Name="Redis:Configuration" Value="Redis connection string" />

    <!-- API side setting -->
    <Parameter Name="Authentication:AzureAd:B2C:Audience" Value="Your Azure AD B2C API app client ID" />
    
    <!-- API and client side settings -->
    <Parameter Name="Authentication:AzureAd:B2C:Instance" Value="Your Azure AD B2C instance, e.g. https://login.microsoftonline.com/" />
    <Parameter Name="Authentication:AzureAd:B2C:TenantId" Value="Your Azure AD B2C tenant, e.g. devunleashedb2c.onmicrosoft.com" />
    <Parameter Name="Authentication:AzureAd:B2C:ClientId" Value="Your Azure AD B2C app client ID" />
    <Parameter Name="Authentication:AzureAd:B2C:ClientSecret" Value="Your Azure AD B2C app client secret" />
    <Parameter Name="Authentication:AzureAd:B2C:PostLogoutRedirectUri" Value="e.g. https://localhost:8686/" />
    <Parameter Name="Authentication:AzureAd:B2C:ApiIdentifier" Value="Your Azure AD B2C API app identifer" />
    <Parameter Name="Authentication:AzureAd:B2C:Policies:SignInOrSignUpPolicy" Value="B2C_1_TestSignUpAndSignInPolicy" />
    <Parameter Name="Authentication:AzureAd:B2C:Policies:EditProfilePolicy" Value="B2C_1_TestProfileEditPolicy" />
    <Parameter Name="Authentication:AzureAd:B2C:Policies:ResetPasswordPolicy" Value="B2C_1_TestPasswordReset" />

    <Parameter Name="Authentication:AzureAd:ApiApp:Instance" Value="Your Azure AD instance, e.g. https://login.microsoftonline.com/" />
    <Parameter Name="Authentication:AzureAd:ApiApp:Audience" Value="BookFast API AppId in Azure AD, e.g. https://devunleashed.onmicrosoft.com/book-fast-api" />
    <Parameter Name="Authentication:AzureAd:ApiApp:ValidIssuers" Value="Comma separated list of tenant identifiers, e.g. https://sts.windows.net/490789ec-b183-4ba5-97cf-e69ec8870130/,https://sts.windows.net/f418e7eb-0dcd-40be-9b81-c58c87c57d9a/" />
   
    <Parameter Name="Authentication:AzureAd:WebApp:ApiResource" Value="BookFast API AppId in Azure AD, e.g. https://devunleashed.onmicrosoft.com/book-fast-api" />
    <Parameter Name="Authentication:AzureAd:WebApp:Instance" Value="Your Azure AD instance, e.g. https://login.microsoftonline.com/" />
    <Parameter Name="Authentication:AzureAd:WebApp:ValidIssuers" Value="Comma separated list of tenant identifiers, e.g. https://sts.windows.net/490789ec-b183-4ba5-97cf-e69ec8870130/,https://sts.windows.net/f418e7eb-0dcd-40be-9b81-c58c87c57d9a/" />
    <Parameter Name="Authentication:AzureAd:WebApp:ClientId" Value="Your BookFast app's client ID" />
    <Parameter Name="Authentication:AzureAd:WebApp:ClientSecret" Value="Your BookFast app's client secret" />
    <Parameter Name="Authentication:AzureAd:WebApp:PostLogoutRedirectUri" Value="e.g. https://localhost:8686/" />
    
  </Parameters>
</Application>

Please inspect service and application manifests to understand how these parameters are used to configure services.

Azure AD

Azure AD is used for organizational accounts of facility providers. You will need two applications in Azure AD: one for the APIs (Book Fast API app) and one for the web (BookFast app). Both applications should have multitenant support enabled. BookFast should have a delegated permission to access BookFast API app. If you're new to Azure AD the following post are going to help you out:

Both apps have a user role called 'Facility Provider' that should be assigned to users to enable them to edit facilities. Please have a look at this post to understand how application and user roles are configured in Azure AD.

Azure AD B2C

Customer accounts are managed in Azure AD B2C. It supports self sign up, profile editing and 3rd part identity providers.

You will need to create a B2C tenant and an app. You will also need to policies:

  1. Sign in or sign up policy
  2. Profile edit policy

You may also find this post useful when setting you your application.

SQL Database

BookFast.Facility.Data contain EFCore migrations to set up you SQL database schema.

Service Bus

Azure Service Bus is used as message broker for integration events.

Please make sure to provision a single topic with 3 subscriptions:

  • Booking
  • Facility
  • SearchIndexer

Also provision 2 notification queues:

  • bookfast-facility-notifications
  • bookfast-booking-notifications

Azure Search

BookFast.Search.Adapter can be run from the command line as dotnet run provision in order to create an index in your Azure Search service. It will require the following parameters to be defined in user secrets:

  • Search:ServiceName
  • Search:AdminKey
  • Search:IndexName

Circuit Breaker

BookingProxy (web app) implements a Circuit Breaker pattern. In order to test it, set Test:FailRandom parameter of the Booking service to true.

Service clients

Each service provides a client library that makes it easier to consumers to communicate with them. The client libraries also implement necessary components for service endpoint resolution.

Using ServicePartitionClient

internal class FacilityProxy : IFacilityService
{
    private readonly IFacilityMapper mapper;
    private readonly IPartitionClientFactory<CommunicationClient<IBookFastFacilityAPI>> partitionClientFactory;

    public FacilityProxy(IFacilityMapper mapper,
        IPartitionClientFactory<CommunicationClient<IBookFastFacilityAPI>> partitionClientFactory)
    {
        this.mapper = mapper;
        this.partitionClientFactory = partitionClientFactory;
    }

    public async Task<Contracts.Models.Facility> FindAsync(Guid facilityId)
    {
        var result = await partitionClientFactory.CreatePartitionClient().InvokeWithRetryAsync(async client =>
        {
            var api = await client.CreateApiClient();
            return await api.FindFacilityWithHttpMessagesAsync(facilityId);
        });

        if (result.Response.StatusCode == HttpStatusCode.NotFound)
        {
            throw new FacilityNotFoundException(facilityId);
        }

        return mapper.MapFrom(result.Body);
    }
}

A consuming service should provide the following configuration section for the target service:

<Section Name="FacilityApi">
  <Parameter Name="ServiceUri" Value="fabric:/BookFast/FacilityService" />
  <Parameter Name="ServiceApiResource" Value="App ID URI of the API app in Azure AD" />
</Section>

Using Reverse Proxy

internal class FacilityProxy : IFacilityService
{
    private readonly IFacilityMapper mapper;
    private readonly IApiClientFactory<IBookFastFacilityAPI> apiClientFactory;

    public FacilityProxy(IFacilityMapper mapper,
        IApiClientFactory<IBookFastFacilityAPI> apiClientFactory)
    {
        this.mapper = mapper;
        this.apiClientFactory = apiClientFactory;
    }

    public async Task<Contracts.Models.Facility> FindAsync(Guid facilityId)
    {
        var api = await apiClientFactory.CreateApiClientAsync();
        var result = await api.FindFacilityWithHttpMessagesAsync(facilityId);

        if (result.Response.StatusCode == HttpStatusCode.NotFound)
        {
            throw new FacilityNotFoundException(facilityId);
        }

        return mapper.MapFrom(result.Body);
    }
}

The ServiceUri setting in this case points to the Reverse Proxy, e.g. http://localhost:19081/BookFast/FacilityService/.