Skip to content

Commit

Permalink
Merge pull request #80 from marcwittke/hotfix/5.1.12
Browse files Browse the repository at this point in the history
Hotfix/5.1.12
  • Loading branch information
marcwittke committed Aug 23, 2019
2 parents 4b88ff0 + 2b201de commit 1a3177a
Show file tree
Hide file tree
Showing 13 changed files with 145 additions and 48 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Backend.Fx.Environment.Persistence
{
public interface IDatabaseUtil
{
bool WaitUntilAvailable(int retries, Func<int, TimeSpan> sleepDurationProvider);
Task<bool> WaitUntilAvailableAsync(int retries, Func<int, TimeSpan> sleepDurationProvider, CancellationToken cancellationToken = default);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,19 @@ protected BackendFxApplication(ICompositionRoot compositionRoot, ITenantIdServic
public ITenantIdService TenantIdService { get; }

/// <inheritdoc />
public async Task Boot()
public async Task Boot(CancellationToken cancellationToken = default)
{
Logger.Info("Booting application");
await OnBoot();
await OnBoot(cancellationToken);
CompositionRoot.Verify();

await OnBooted();
await OnBooted(cancellationToken);
_isBooted.Set();
}

public Task<bool> WaitForBootAsync(int timeoutMilliSeconds = int.MaxValue)
public bool WaitForBoot(int timeoutMilliSeconds = int.MaxValue, CancellationToken cancellationToken = default)
{
return Task.Run(() => WaitForBoot(timeoutMilliSeconds));
}

public bool WaitForBoot(int timeoutMilliSeconds = int.MaxValue)
{
return _isBooted.Wait(timeoutMilliSeconds);
return _isBooted.Wait(timeoutMilliSeconds, cancellationToken);
}

public IDisposable BeginScope(IIdentity identity = null, TenantId tenantId = null)
Expand Down Expand Up @@ -92,12 +87,32 @@ public IDisposable BeginScope(IIdentity identity = null, TenantId tenantId = nul
Invoke(() => CompositionRoot.GetInstance<TJob>().Run(), new SystemIdentity(), tenantId);
}

public Task InvokeAsync(Action action, IIdentity identity, TenantId tenantId)
public void Invoke(Action action, IIdentity identity, TenantId tenantId)
{
return Task.Run(() => Invoke(action, identity, tenantId));
using (BeginScope(new SystemIdentity(), tenantId))
{
using (var unitOfWork = CompositionRoot.GetInstance<IUnitOfWork>())
{
try
{
unitOfWork.Begin();
action.Invoke();
unitOfWork.Complete();
}
catch (TargetInvocationException ex)
{
ExceptionLogger.LogException(ex.InnerException ?? ex);
}
catch (Exception ex)
{
Logger.Info(ex);
ExceptionLogger.LogException(ex);
}
}
}
}

public void Invoke(Action action, IIdentity identity, TenantId tenantId)
public async Task InvokeAsync(Action action, IIdentity identity, TenantId tenantId, CancellationToken cancellationToken = default)
{
using (BeginScope(new SystemIdentity(), tenantId))
{
Expand All @@ -106,7 +121,7 @@ public void Invoke(Action action, IIdentity identity, TenantId tenantId)
try
{
unitOfWork.Begin();
action.Invoke();
await Task.Factory.StartNew(action, cancellationToken);
unitOfWork.Complete();
}
catch (TargetInvocationException ex)
Expand All @@ -121,21 +136,23 @@ public void Invoke(Action action, IIdentity identity, TenantId tenantId)
}
}
}

/// <summary>
/// Extension point to do additional initialization before composition root is initialized
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected virtual async Task OnBoot()
protected virtual async Task OnBoot(CancellationToken cancellationToken)
{
await Task.CompletedTask;
}

/// <summary>
/// Extension point to do additional initialization after composition root is initialized
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected virtual async Task OnBooted()
protected virtual async Task OnBooted(CancellationToken cancellationToken)
{
await Task.CompletedTask;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;
using Backend.Fx.Environment.MultiTenancy;
using Backend.Fx.Environment.Persistence;
using Backend.Fx.Logging;
Expand Down Expand Up @@ -29,21 +30,24 @@ public abstract class BackendFxDbApplication : BackendFxApplication
/// </summary>
public IDatabaseBootstrapper DatabaseBootstrapper { get; }

protected sealed override async Task OnBoot()
protected sealed override async Task OnBoot(CancellationToken cancellationToken)
{
await OnDatabaseBoot();
WaitForDatabase();
await OnDatabaseBoot(cancellationToken);
await WaitForDatabase(cancellationToken);
DatabaseBootstrapper.EnsureDatabaseExistence();
await OnDatabaseBooted();
await OnDatabaseBooted(cancellationToken);
}

protected virtual void WaitForDatabase() { }
protected virtual async Task WaitForDatabase(CancellationToken cancellationToken)
{
await Task.CompletedTask;
}

/// <summary>
/// Extension point to do additional initialization before existence of database is ensured
/// </summary>
/// <returns></returns>
protected virtual async Task OnDatabaseBoot()
protected virtual async Task OnDatabaseBoot(CancellationToken cancellationToken)
{
await Task.CompletedTask;
}
Expand All @@ -52,7 +56,7 @@ protected virtual async Task OnDatabaseBoot()
/// Extension point to do additional initialization after existence of database is ensured
/// </summary>
/// <returns></returns>
protected virtual async Task OnDatabaseBooted()
protected virtual async Task OnDatabaseBooted(CancellationToken cancellationToken)
{
await Task.CompletedTask;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using Backend.Fx.Environment.MultiTenancy;
using Backend.Fx.Logging;
Expand All @@ -24,27 +25,22 @@ public interface IBackendFxApplication : IDisposable
/// </summary>
ITenantIdService TenantIdService { get; }

/// <summary>
/// allows asynchronously awaiting application startup
/// </summary>
Task<bool> WaitForBootAsync(int timeoutMilliSeconds = int.MaxValue);

/// <summary>
/// allows synchronously awaiting application startup
/// </summary>
bool WaitForBoot(int timeoutMilliSeconds = int.MaxValue);
bool WaitForBoot(int timeoutMilliSeconds = int.MaxValue, CancellationToken cancellationToken = default);

/// <summary>
/// Initializes ans starts the application (async)
/// </summary>
/// <returns></returns>
Task Boot();
Task Boot(CancellationToken cancellationToken = default);

IDisposable BeginScope(IIdentity identity = null, TenantId tenantId = null);

void Invoke(Action action, IIdentity identity, TenantId tenantId);

Task InvokeAsync(Action action, IIdentity identity, TenantId tenantId);
Task InvokeAsync(Action action, IIdentity identity, TenantId tenantId, CancellationToken cancellationToken = default);

void Run<TJob>() where TJob : class, IJob;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,6 @@ public void Complete()
_isCompleted = true;
}



protected abstract void UpdateTrackingProperties(string userId, DateTime utcNow);
protected abstract void Commit();
protected abstract void Rollback();
Expand All @@ -76,7 +74,7 @@ protected virtual void Dispose(bool disposing)
{
if (_isCompleted == false)
{
Logger.Info($"Canceling unit of work #{_instanceId} because the instance is being disposed although it did not complete before. This should only occur during cleanup after errors.");
Logger.Info($"Canceling unit of work #{_instanceId}.");
Rollback();
}
_lifetimeLogger?.Dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public void Begin()

public void Complete()
{
//skip
// prevent completion, results in rollback on disposal
}

public ICurrentTHolder<IIdentity> IdentityHolder => _unitOfWorkImplementation.IdentityHolder;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Backend.Fx.Patterns.DependencyInjection;
using Backend.Fx.Patterns.UnitOfWork;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Backend.Fx.AspNetCore.Mvc.UnitOfWork
{
public class UnitOfWorkFilter : IActionFilter
{
private readonly IBackendFxApplication _application;

public UnitOfWorkFilter(IBackendFxApplication application)
{
_application = application;
}

public void OnActionExecuting(ActionExecutingContext context)
{
var unitOfWork = _application.CompositionRoot.GetInstance<IUnitOfWork>();
if (context.HttpContext.Request.IsSafe())
{
unitOfWork = new ReadonlyDecorator(unitOfWork);
}
unitOfWork.Begin();
}

public void OnActionExecuted(ActionExecutedContext context)
{
var unitOfWork = _application.CompositionRoot.GetInstance<IUnitOfWork>();

try
{
unitOfWork.Complete();
}
finally
{
unitOfWork.Dispose();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public WaitForBootMiddleware(RequestDelegate next, IBackendFxApplication applica
[UsedImplicitly]
public async Task Invoke(HttpContext context)
{
while (!await _application.WaitForBootAsync(3000))
while (!_application.WaitForBoot(3000))
{
Logger.Info("Queuing Request while application is booting...");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
namespace Backend.Fx.AspNetCore.UnitOfWork
{
/// <summary>
/// The Middleware is responsible for beginning and completing (or disposing) the unit of work for each request.
/// The Middleware is responsible for beginning and completing (or disposing) the unit of work for each request.
/// In context of an MVC application you should not use a middleware, but the UnitOfWorkActionFilter.
/// </summary>
public class UnitOfWorkMiddleware
{
Expand All @@ -29,15 +30,11 @@ public UnitOfWorkMiddleware(RequestDelegate next, IBackendFxApplication applicat
[UsedImplicitly]
public async Task Invoke(HttpContext context)
{
while (!await _application.WaitForBootAsync(3000))
{
Logger.Info("Queuing Request while application is booting...");
}

IUnitOfWork unitOfWork = _application.CompositionRoot.GetInstance<IUnitOfWork>();
try
{
if (context.Request.Method.ToUpperInvariant() == "GET")
// Safe requests (GET, HEAD, OPTIONS) are handled using a unit of work that gets never completed
if (context.Request.IsSafe())
{
unitOfWork = new ReadonlyDecorator(unitOfWork);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Reflection;
using System.Security.Principal;
using Backend.Fx.Environment.Authentication;
using Backend.Fx.Environment.MultiTenancy;
using Backend.Fx.Logging;
using Backend.Fx.Patterns.DataGeneration;
using Backend.Fx.Patterns.DependencyInjection;
using Backend.Fx.Patterns.EventAggregation.Domain;
using Backend.Fx.Patterns.EventAggregation.Integration;
Expand Down Expand Up @@ -42,13 +44,17 @@ protected override void Register(Container container, ScopedLifestyle scopedLife
Logger.Debug($"Registering {nameof(CurrentTenantIdHolder)} as {nameof(ICurrentTHolder<TenantId>)}");
container.Register<ICurrentTHolder<TenantId>, CurrentTenantIdHolder>();

// domain event subsystem
Logger.Debug("Registering event aggregator");
container.Register<IDomainEventAggregator>(_domainEventAggregatorFactory);

// integration event subsystem
Logger.Debug("Registering event bus");
container.RegisterInstance(_eventBus);

// initial data generators collection (using the current assembly causes an empty collection)
container.Collection.Register<IDataGenerator>(typeof(InfrastructureModule).GetTypeInfo().Assembly);

container.Register<IEventBusScope, EventBusScope>();

}
Expand Down
36 changes: 36 additions & 0 deletions src/implementations/Backend.Fx.SqlServer/MsSqlServerUtil.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Data.SqlClient;
using System.Threading;
using System.Threading.Tasks;
using Backend.Fx.Environment.Persistence;
using Backend.Fx.Logging;
using Polly;
Expand Down Expand Up @@ -107,6 +109,40 @@ public bool WaitUntilAvailable(int retries, Func<int, TimeSpan> sleepDurationPro
});
}

public async Task<bool> WaitUntilAvailableAsync(int retries, Func<int, TimeSpan> sleepDurationProvider, CancellationToken cancellationToken = default)
{
Logger.Info($"Probing for SQL instance with {retries} retries.");
SqlConnectionStringBuilder sb = new SqlConnectionStringBuilder(ConnectionString) { InitialCatalog = "master" };
return await Policy
.HandleResult<bool>(result => result == false)
.WaitAndRetryAsync(retries, sleepDurationProvider)
.ExecuteAsync(async () =>
{
if (cancellationToken.IsCancellationRequested)
{
Logger.Info("Waiting until database is available was cancelled");
return false;
}
try
{
using (var connection = new SqlConnection(sb.ConnectionString))
{
connection.Open();
var command = connection.CreateCommand();
command.CommandText = "SELECT count(*) FROM sys.databases WHERE Name = 'master'";
await command.ExecuteScalarAsync(cancellationToken);
return true;
}
}
catch (Exception ex)
{
Logger.Info(ex, "No MSSQL instance was found");
return false;
}
});
}

public void EnsureExistingDatabase()
{
SqlConnectionStringBuilder sb = new SqlConnectionStringBuilder(ConnectionString);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public void EnsureDemoTenant()

public TenantId DemoTenantId { get; private set; }

protected override Task OnBooted()
protected override Task OnBooted(CancellationToken cancellationToken)
{
var tenantService = new TenantService(CompositionRoot.GetInstance<IEventBus>(), _tenantRepository);
this.RegisterSeedActionForNewlyCreatedTenants(tenantService);
Expand Down

0 comments on commit 1a3177a

Please sign in to comment.