diff --git a/DeepTransaction/BaseContextualTransaction.cs b/DeepTransaction/BaseContextualTransaction.cs new file mode 100644 index 0000000..115dc17 --- /dev/null +++ b/DeepTransaction/BaseContextualTransaction.cs @@ -0,0 +1,15 @@ +using System; +using DeepTransaction.TransactionScope; + +namespace DeepTransaction +{ + public class BaseContextualTransaction : BaseTransaction, IContextStep where TContext : class, IDisposable + { + protected BaseContextualTransaction(string name, TContext context) : base(name) + { + _tran = new ContextualTransaction(name, context); + } + + public TContext Context { get; set; } + } +} \ No newline at end of file diff --git a/DeepTransaction/BaseTransaction.cs b/DeepTransaction/BaseTransaction.cs index 8e7a95d..aff1e41 100644 --- a/DeepTransaction/BaseTransaction.cs +++ b/DeepTransaction/BaseTransaction.cs @@ -2,7 +2,7 @@ { public abstract class BaseTransaction : ITransactionStep { - private readonly TransactionWorker _tran; + protected TransactionEngine _tran; /// /// Defines the transaction name @@ -10,7 +10,7 @@ public abstract class BaseTransaction : ITransactionStep /// Transaction Name protected BaseTransaction(string name) { - this._tran = TransactionWorker.Define(name); + this._tran = new TransactionEngine(name); } /// diff --git a/DeepTransaction/ContextualTransaction.cs b/DeepTransaction/ContextualTransaction.cs new file mode 100644 index 0000000..fd98d84 --- /dev/null +++ b/DeepTransaction/ContextualTransaction.cs @@ -0,0 +1,55 @@ +using System; +using DeepTransaction.DI; +using DeepTransaction.TransactionScope; + +namespace DeepTransaction +{ + public class ContextualTransaction : TransactionEngine where TContext : class, IDisposable + { + private readonly TContext _context; + private readonly bool _isParent; + private readonly ITransactionScope _transactionScope; + + internal ContextualTransaction(string name, TContext context, bool isParent = false) : base(name) + { + _context = context; + _isParent = isParent; + _transactionScope = (ITransactionScope)DeepBootstrapper.GetContext(); + } + + private void ExecuteIfParent(Action action) + { + if (_isParent) action(); + } + + public override TransactionContext Process(TransactionContext input) + { + foreach (var transactionStep in _steps) + { + if (transactionStep is IContextStep step) + { + step.Context = _context; + } + } + + ExecuteIfParent(() => _transactionScope.BeginTran(_context)); + + TransactionContext outputContext; + + try + { + using (_context) + { + outputContext = base.Process(input); + ExecuteIfParent(() => _transactionScope.Commit(_context)); + } + } + catch (Exception) + { + _transactionScope.Rollback(_context); + throw; + } + return outputContext; + } + } +} \ No newline at end of file diff --git a/DeepTransaction/DI/DeepBootstrapper.cs b/DeepTransaction/DI/DeepBootstrapper.cs index aa36f9f..c1dd4b6 100644 --- a/DeepTransaction/DI/DeepBootstrapper.cs +++ b/DeepTransaction/DI/DeepBootstrapper.cs @@ -7,7 +7,8 @@ public static class DeepBootstrapper { private static IDependencyResolver _dependencyResolver; private static IListener _listener; - + private static object _context; + /// /// This method will register your dependecy resolver in order to take advantage of IOC /// @@ -26,6 +27,11 @@ public static void MapListener(IListener listener) _listener = listener; } + public static void MapContext(object context) + { + _context = context; + } + internal static IDependencyResolver Get() { if (_dependencyResolver == null) @@ -40,5 +46,10 @@ internal static IListener GetListener() { return _listener; } + + internal static object GetContext() + { + return _context; + } } } \ No newline at end of file diff --git a/DeepTransaction/DeepTransaction.csproj b/DeepTransaction/DeepTransaction.csproj index bb6bd1d..993952a 100644 --- a/DeepTransaction/DeepTransaction.csproj +++ b/DeepTransaction/DeepTransaction.csproj @@ -33,16 +33,16 @@ - - + + @@ -50,6 +50,9 @@ + + + diff --git a/DeepTransaction/TransactionEngine.cs b/DeepTransaction/TransactionEngine.cs new file mode 100644 index 0000000..c1e9f1d --- /dev/null +++ b/DeepTransaction/TransactionEngine.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using DeepTransaction.DI; +using DeepTransaction.Listeners; + +namespace DeepTransaction +{ + public class TransactionEngine + { + private readonly string _name; + protected readonly Queue _steps; + private readonly IDependencyResolver _dependencyResolver; + private IListener _listener; + + internal TransactionEngine(string name) + { + _steps = new Queue(); + _dependencyResolver = DeepBootstrapper.Get(); + _listener = DeepBootstrapper.GetListener(); + this._name = name; + } + + /// + /// This method is using for chaning steps for the current transaction + /// + /// The type of the step + /// A base transaction used for chainging + public TransactionEngine AddStep() where TStep : ITransactionStep + { + var step = _dependencyResolver.Resolve(); + this._steps.Enqueue(step); + + return this; + } + + /// + /// This method is for passing the context and execute the current transaction + /// + /// Input Context which has to have the type Transaction Context + /// + public virtual TransactionContext Process(TransactionContext input) + { + var stepName = string.Empty; + dynamic previousOutput = null; + while (_steps.Count > 0) + { + try + { + var step = _steps.Dequeue(); + stepName = step.GetType().FullName; + + _listener?.Before(new ListenerModel() { Context = input, StepName = stepName, TransactionName = _name }); + + step.Before(input); + previousOutput = step.Execute(input); + + _listener?.After(new ListenerModel() { Context = input, StepName = stepName, TransactionName = _name }); + } + catch (System.Exception e) + { + _listener?.OnError(new ListenerModel() { Context = input, StepName = stepName, TransactionName = _name }, e); + throw; + } + } + + return previousOutput; + } + + /// + /// Register listener to a specific transaction. If a global listener was already registered this method will overided it for this specific transaction + /// + /// IListener instance implementation + /// + public TransactionEngine WithListener(IListener listener) + { + this._listener = listener; + return this; + } + } +} \ No newline at end of file diff --git a/DeepTransaction/TransactionScope/IContextStep.cs b/DeepTransaction/TransactionScope/IContextStep.cs new file mode 100644 index 0000000..dbd7ff4 --- /dev/null +++ b/DeepTransaction/TransactionScope/IContextStep.cs @@ -0,0 +1,9 @@ +using System; + +namespace DeepTransaction.TransactionScope +{ + public interface IContextStep where T : IDisposable + { + T Context { get; set; } + } +} \ No newline at end of file diff --git a/DeepTransaction/TransactionScope/ITransactionScope.cs b/DeepTransaction/TransactionScope/ITransactionScope.cs new file mode 100644 index 0000000..01e0c63 --- /dev/null +++ b/DeepTransaction/TransactionScope/ITransactionScope.cs @@ -0,0 +1,13 @@ +using System; + +namespace DeepTransaction.TransactionScope +{ + public interface ITransactionScope where TContext : IDisposable + { + void BeginTran(TContext context); + + void Commit(TContext context); + + void Rollback(TContext context); + } +} \ No newline at end of file diff --git a/DeepTransaction/TransactionWorker.cs b/DeepTransaction/TransactionWorker.cs index 0d70e20..67a5f27 100644 --- a/DeepTransaction/TransactionWorker.cs +++ b/DeepTransaction/TransactionWorker.cs @@ -1,84 +1,17 @@ -using System.Collections.Generic; -using DeepTransaction.DI; -using DeepTransaction.Listeners; +using System; namespace DeepTransaction { public class TransactionWorker { - private readonly string _name; - private readonly Queue _steps; - private readonly IDependencyResolver _dependencyResolver; - private IListener _listener; - - public static TransactionWorker Define(string name) - { - return new TransactionWorker(name); - } - - private TransactionWorker(string name) + public static TransactionEngine Define(string name) { - _steps = new Queue(); - _dependencyResolver = DeepBootstrapper.Get(); - _listener = DeepBootstrapper.GetListener(); - this._name = name; - } - - /// - /// This method is using for chaning steps for the current transaction - /// - /// The type of the step - /// A base transaction used for chainging - public TransactionWorker AddStep() where TStep : ITransactionStep - { - var step = _dependencyResolver.Resolve(); - this._steps.Enqueue(step); - - return this; - } - - /// - /// This method is for passing the context and execute the current transaction - /// - /// Input Context which has to have the type Transaction Context - /// - public TransactionContext Process(TransactionContext input) - { - var stepName = string.Empty; - dynamic previousOutput = null; - while (_steps.Count > 0) - { - try - { - var step = _steps.Dequeue(); - stepName = step.GetType().FullName; - - _listener?.Before(new ListenerModel() { Context = input, StepName = stepName, TransactionName = _name }); - - step.Before(input); - previousOutput = step.Execute(input); - - _listener?.After(new ListenerModel() { Context = input, StepName = stepName, TransactionName = _name }); - } - catch (System.Exception e) - { - _listener?.OnError(new ListenerModel() { Context = input, StepName = stepName, TransactionName = _name }, e); - throw; - } - } - - return previousOutput; + return new TransactionEngine(name); } - /// - /// Register listener to a specific transaction. If a global listener was already registered this method will overided it for this specific transaction - /// - /// IListener instance implementation - /// - public TransactionWorker WithListener(IListener listener) + public static ContextualTransaction Define(string name, TContext context) where TContext : class, IDisposable { - this._listener = listener; - return this; + return new ContextualTransaction(name, context, true); } } } diff --git a/Transaction.Test/Container.cs b/Transaction.Test/Container.cs index c00b3cb..e448d5e 100644 --- a/Transaction.Test/Container.cs +++ b/Transaction.Test/Container.cs @@ -8,23 +8,11 @@ public sealed class Container { #region Private Fields - /// - /// The instance of the IoC Container. - /// - private static readonly Container instance = new Container(); - private readonly IUnityContainer iocContainer = new UnityContainer(); - #endregion #region Constructor - public IUnityContainer IocContainer - { - get - { - return iocContainer; - } - } + public IUnityContainer IocContainer { get; } = new UnityContainer(); /// /// Prevents the creation of an instance of the Container type. @@ -38,8 +26,7 @@ private Container() /// static Container() { - Instance.iocContainer.RegisterInstance(Instance, new ContainerControlledLifetimeManager()); - + Instance.IocContainer.RegisterInstance(Instance, new ContainerControlledLifetimeManager()); } #endregion @@ -49,13 +36,7 @@ static Container() /// /// Gets the singleton instance of the Container class. /// - public static Container Instance - { - get - { - return instance; - } - } + public static Container Instance { get; } = new Container(); #endregion @@ -68,7 +49,7 @@ public static Container Instance /// The implementation type. public void Register() where TImplementation : TBase { - this.iocContainer.RegisterType(typeof(TBase), typeof(TImplementation), new TransientLifetimeManager()); + this.IocContainer.RegisterType(typeof(TBase), typeof(TImplementation), new TransientLifetimeManager()); } /// @@ -79,7 +60,7 @@ public void Register() where TImplementation : TBase /// The singleton value. public void Register(TImplementation value) where TImplementation : TBase { - this.iocContainer.RegisterInstance(typeof(TBase), value, new ContainerControlledLifetimeManager()); + this.IocContainer.RegisterInstance(typeof(TBase), value, new ContainerControlledLifetimeManager()); } /// @@ -89,7 +70,7 @@ public void Register(TImplementation value) where TImple /// The resolved value. public T Resolve() { - return (T)this.iocContainer.Resolve(typeof(T)); + return (T)this.IocContainer.Resolve(typeof(T)); } /// @@ -106,7 +87,7 @@ public T Resolve(Dictionary parameters) parameterOverrides.Add(parameter.Key, parameter.Value); } - return (T)this.iocContainer.Resolve(typeof(T), parameterOverrides.OnType()); + return (T)this.IocContainer.Resolve(typeof(T), parameterOverrides.OnType()); } /// @@ -116,7 +97,7 @@ public T Resolve(Dictionary parameters) /// public void RegisterInConstructor() { - this.iocContainer.RegisterType(new InjectionConstructor(Instance.Resolve())); + this.IocContainer.RegisterType(new InjectionConstructor(Instance.Resolve())); } /// @@ -126,7 +107,7 @@ public void RegisterInConstructor() /// Open Generic Implementation public void Register(Type basic, Type implementation) { - this.iocContainer.RegisterType(basic, implementation, new TransientLifetimeManager()); + this.IocContainer.RegisterType(basic, implementation, new TransientLifetimeManager()); } #endregion diff --git a/Transaction.Test/DataProviders/TransactionalSteps.cs b/Transaction.Test/DataProviders/TransactionalSteps.cs new file mode 100644 index 0000000..09b6e05 --- /dev/null +++ b/Transaction.Test/DataProviders/TransactionalSteps.cs @@ -0,0 +1,43 @@ +using System; +using DeepTransaction; +using DeepTransaction.TransactionScope; + +namespace Transaction.Test.DataProviders +{ + public class MyContext : IDisposable + { + public virtual void Dispose() + { + } + + public virtual void BeginTran() + { + + } + + public virtual void Rollback() + { + + } + + public virtual void Commit() + { + + } + } + + public class TransactionStep : ITransactionStep, IContextStep + { + public void Before(dynamic input) + { + + } + + public TransactionContext Execute(dynamic input) + { + return input; + } + + public MyContext Context { get; set; } + } +} \ No newline at end of file diff --git a/Transaction.Test/Implementations/TransactionScopeImpl.cs b/Transaction.Test/Implementations/TransactionScopeImpl.cs new file mode 100644 index 0000000..c674187 --- /dev/null +++ b/Transaction.Test/Implementations/TransactionScopeImpl.cs @@ -0,0 +1,23 @@ +using DeepTransaction.TransactionScope; +using Transaction.Test.DataProviders; + +namespace Transaction.Test.Implementations +{ + public class TransactionScopeImpl : ITransactionScope + { + public void BeginTran(MyContext context) + { + context.BeginTran(); + } + + public void Commit(MyContext context) + { + context.Commit(); + } + + public void Rollback(MyContext context) + { + context.Rollback(); + } + } +} \ No newline at end of file diff --git a/Transaction.Test/Tests/TransactionalPipelinesTest.cs b/Transaction.Test/Tests/TransactionalPipelinesTest.cs new file mode 100644 index 0000000..06fcefe --- /dev/null +++ b/Transaction.Test/Tests/TransactionalPipelinesTest.cs @@ -0,0 +1,24 @@ +using DeepTransaction; +using DeepTransaction.DI; +using Transaction.Test.DataProviders; +using Transaction.Test.Implementations; +using Xunit; + +namespace Transaction.Test.Tests +{ + public class TransactionalPipelinesTest : BaseTest + { + public TransactionalPipelinesTest() + { + DeepBootstrapper.MapContext(new TransactionScopeImpl()); + } + + [Fact] + public void it_will_instantiate_ContextualTransaction() + { + TransactionWorker.Define("My transactional transaction", new MyContext()) + .AddStep() + .Process(new TransactionContext()); + } + } +} \ No newline at end of file diff --git a/Transaction.Test/Transaction.Test.csproj b/Transaction.Test/Transaction.Test.csproj index a1d10ed..da29184 100644 --- a/Transaction.Test/Transaction.Test.csproj +++ b/Transaction.Test/Transaction.Test.csproj @@ -74,6 +74,8 @@ + + @@ -84,6 +86,7 @@ + diff --git a/Transactions.sln b/Transactions.sln index 4a2f830..38043f4 100644 --- a/Transactions.sln +++ b/Transactions.sln @@ -1,13 +1,15 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26403.7 +VisualStudioVersion = 15.0.27004.2002 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Transaction.Test", "Transaction.Test\Transaction.Test.csproj", "{4A5946C9-CB75-4E2F-ADD8-4BABF6AB1CAD}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeepTransaction", "DeepTransaction\DeepTransaction.csproj", "{DC46A61C-013C-449B-BAC4-541076527456}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeepTransactionCore", "DeepTransactionCore\DeepTransactionCore.csproj", "{18855774-4CC7-4220-BA0E-006948FF8F2C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeepTransactionCore", "DeepTransactionCore\DeepTransactionCore.csproj", "{18855774-4CC7-4220-BA0E-006948FF8F2C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeepTransaction.EntityFramework.Test", "DeepTransaction.EntityFramework.Test\DeepTransaction.EntityFramework.Test.csproj", "{8BC13104-62E2-4044-8091-E5B12FBE7824}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -27,8 +29,15 @@ Global {18855774-4CC7-4220-BA0E-006948FF8F2C}.Debug|Any CPU.Build.0 = Debug|Any CPU {18855774-4CC7-4220-BA0E-006948FF8F2C}.Release|Any CPU.ActiveCfg = Release|Any CPU {18855774-4CC7-4220-BA0E-006948FF8F2C}.Release|Any CPU.Build.0 = Release|Any CPU + {8BC13104-62E2-4044-8091-E5B12FBE7824}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BC13104-62E2-4044-8091-E5B12FBE7824}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BC13104-62E2-4044-8091-E5B12FBE7824}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BC13104-62E2-4044-8091-E5B12FBE7824}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8C38CC06-FDA9-4302-B0E5-F7CBA24A3ED2} + EndGlobalSection EndGlobal