├── .gitignore ├── README.md └── src ├── CommandAndQueryHandlers ├── CommandAndQueryHandlers.sln └── CommandAndQueryHandlers │ ├── Bootstrapper.cs │ ├── CommandAndQueryHandlers.csproj │ ├── DB.cs │ ├── DeleteUserCommand.cs │ ├── DeleteUserCommandHandler.cs │ ├── ICommand.cs │ ├── ICommandHandler.cs │ ├── IMediate.cs │ ├── IQuery.cs │ ├── IQueryHandler.cs │ ├── InsertUserCommand.cs │ ├── InsertUserCommandHandler.cs │ ├── Mediator.cs │ ├── Program.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── Startup.cs │ ├── UpdateUserCommand.cs │ ├── UpdateUserCommandHandler.cs │ ├── User.cs │ ├── UserModule.cs │ ├── UserQuery.cs │ ├── UserQueryHandler.cs │ ├── UserValidator.cs │ └── packages.config ├── DbConnectionAndCommands ├── DbConnectionAndCommands.sln ├── DbConnectionAndCommands │ ├── App.config │ ├── Bootstrapper.cs │ ├── DbConnectionAndCommands.csproj │ ├── HomeModule.cs │ ├── IDbConnectionProvider.cs │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── SqlServerConnectionProvider.cs │ ├── Startup.cs │ ├── User.cs │ └── packages.config └── db.sql ├── MediatR ├── MediatR.sln └── MediatR │ ├── Commands.cs │ ├── MediatR.csproj │ ├── NancyExtensions.cs │ ├── Program.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── Queries.cs │ └── packages.config ├── MultiDbSupport ├── MultiDbSupport.sln ├── MultiDbSupport │ ├── App.config │ ├── Bootstrapper.cs │ ├── HomeModule.cs │ ├── IDbConnectionProvider.cs │ ├── MultiDbSupport.csproj │ ├── PostgresConnectionProvider.cs │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── SqlServerConnectionProvider.cs │ ├── Startup.cs │ ├── User.cs │ ├── UserListQuery.cs │ ├── UserListQueryRequestHandler.cs │ └── packages.config └── db.sql ├── MultiDbSupportWithConventions ├── MultiDbSupportWithConventions.Tests │ ├── App.config │ ├── Features │ │ └── Users │ │ │ ├── AddUser │ │ │ └── AddUserTests.cs │ │ │ ├── GetUser │ │ │ └── GetUserTests.cs │ │ │ ├── GetUsers │ │ │ └── GetUsersTests.cs │ │ │ └── NoDbAddUserCommandHandler.cs │ ├── MultiDbSupportWithConventions.Tests.csproj │ ├── OurTestBStrapper.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── packages.config ├── MultiDbSupportWithConventions.sln ├── MultiDbSupportWithConventions │ ├── App.config │ ├── Bootstrapper.cs │ ├── Features │ │ └── Users │ │ │ ├── AddUser │ │ │ ├── AddUserCommand.cs │ │ │ ├── AddUserCommandHandler.cs │ │ │ ├── AddUserValidator.cs │ │ │ └── MsSqlAddUserCommandHandler.cs │ │ │ ├── DataAccess │ │ │ ├── AbstractNpgsqlPagingRequestHandler.cs │ │ │ ├── GetUserByIdQuery.cs │ │ │ └── IGetUserByIdQuery.cs │ │ │ ├── DeleteUser │ │ │ ├── DeleteUserCommand.cs │ │ │ └── DeleteUserCommandHandler.cs │ │ │ ├── GetUser │ │ │ ├── AgnosticDbGetUserQueryHandler.cs │ │ │ └── GetUserQuery.cs │ │ │ ├── GetUsers │ │ │ ├── MsSqlUserListQueryRequestHandler.cs │ │ │ ├── NpgsqlUserListQueryRequestHandler.cs │ │ │ └── UserListQuery.cs │ │ │ ├── GetUsersPaged │ │ │ ├── NpgsqlUserListQueryPagingHandler.cs │ │ │ └── PagedUserListQuery.cs │ │ │ ├── UpdateUser │ │ │ ├── UpdateUserCommand.cs │ │ │ └── UpdateUserCommandHandler.cs │ │ │ ├── User.cs │ │ │ └── UserModule.cs │ ├── IDbConnectionProvider.cs │ ├── MultiDbSupportWithConventions.csproj │ ├── PostgresConnectionProvider.cs │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── SqlServerConnectionProvider.cs │ ├── Startup.cs │ └── packages.config └── db.sql └── TraditionalLayering ├── TraditionalLayering.sln ├── TraditionalLayering ├── Model │ └── Person.cs ├── NancyModule │ └── PersonModule.cs ├── Program.cs ├── Repository │ ├── AccountRepository.cs │ └── IAccountRepository.cs ├── Service │ ├── AccountService.cs │ └── IAccountService.cs ├── Startup.cs ├── TraditionalLayering.csproj ├── app.config └── packages.config └── TraditionalLayering_Tests ├── Properties └── AssemblyInfo.cs ├── TraditionalLayering_Tests.csproj ├── When_creating_a_user.cs ├── When_getting_a_user.cs └── packages.config /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studo 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | *_i.c 42 | *_p.c 43 | *_i.h 44 | *.ilk 45 | *.meta 46 | *.obj 47 | *.pch 48 | *.pdb 49 | *.pgc 50 | *.pgd 51 | *.rsp 52 | *.sbr 53 | *.tlb 54 | *.tli 55 | *.tlh 56 | *.tmp 57 | *.tmp_proj 58 | *.log 59 | *.vspscc 60 | *.vssscc 61 | .builds 62 | *.pidb 63 | *.svclog 64 | *.scc 65 | 66 | # Chutzpah Test files 67 | _Chutzpah* 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | *.cachefile 76 | 77 | # Visual Studio profiler 78 | *.psess 79 | *.vsp 80 | *.vspx 81 | 82 | # TFS 2012 Local Workspace 83 | $tf/ 84 | 85 | # Guidance Automation Toolkit 86 | *.gpState 87 | 88 | # ReSharper is a .NET coding add-in 89 | _ReSharper*/ 90 | *.[Rr]e[Ss]harper 91 | *.DotSettings.user 92 | 93 | # JustCode is a .NET coding addin-in 94 | .JustCode 95 | 96 | # TeamCity is a build add-in 97 | _TeamCity* 98 | 99 | # DotCover is a Code Coverage Tool 100 | *.dotCover 101 | 102 | # NCrunch 103 | _NCrunch_* 104 | .*crunch*.local.xml 105 | 106 | # MightyMoose 107 | *.mm.* 108 | AutoTest.Net/ 109 | 110 | # Web workbench (sass) 111 | .sass-cache/ 112 | 113 | # Installshield output folder 114 | [Ee]xpress/ 115 | 116 | # DocProject is a documentation generator add-in 117 | DocProject/buildhelp/ 118 | DocProject/Help/*.HxT 119 | DocProject/Help/*.HxC 120 | DocProject/Help/*.hhc 121 | DocProject/Help/*.hhk 122 | DocProject/Help/*.hhp 123 | DocProject/Help/Html2 124 | DocProject/Help/html 125 | 126 | # Click-Once directory 127 | publish/ 128 | 129 | # Publish Web Output 130 | *.[Pp]ublish.xml 131 | *.azurePubxml 132 | # TODO: Comment the next line if you want to checkin your web deploy settings 133 | # but database connection strings (with potential passwords) will be unencrypted 134 | *.pubxml 135 | *.publishproj 136 | 137 | # NuGet Packages 138 | *.nupkg 139 | # The packages folder can be ignored because of Package Restore 140 | **/packages/* 141 | # except build/, which is used as an MSBuild target. 142 | !**/packages/build/ 143 | # Uncomment if necessary however generally it will be regenerated when needed 144 | #!**/packages/repositories.config 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | *.[Cc]ache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | bower_components/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # Node.js Tools for Visual Studio 190 | .ntvs_analysis.dat 191 | 192 | # Visual Studio 6 build log 193 | *.plg 194 | 195 | # Visual Studio 6 workspace options file 196 | *.opt 197 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LayerAllTheThings 2 | 3 | An attempt to show alternative architectural patterns in CRUD based .Net web applications. 4 | 5 | 1) [Traditional layering architecture](https://github.com/jchannon/LayerAllTheThings/tree/master/src/TraditionalLayering) 6 | 7 | 2) [Command/Query handlers with manual wireup](https://github.com/jchannon/LayerAllTheThings/tree/master/src/CommandAndQueryHandlers) 8 | 9 | 3) [Command/Query handlers with MediatR](https://github.com/jchannon/LayerAllTheThings/tree/master/src/MediatR) 10 | 11 | 4) [IDBConnection injected to Nancy Module](https://github.com/jchannon/LayerAllTheThings/tree/master/src/DbConnectionAndCommands) 12 | 13 | 5) [Multi DB Support with Command/Query handlers & Stored Procedure approach](https://github.com/jchannon/LayerAllTheThings/tree/master/src/MultiDbSupport) 14 | 15 | 6) [Multi DB Support with Command/Query handlers named with DB specific conventions](https://github.com/jchannon/LayerAllTheThings/tree/master/src/MultiDbSupportWithConventions) 16 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommandAndQueryHandlers", "CommandAndQueryHandlers\CommandAndQueryHandlers.csproj", "{43091E5B-7AFC-4D2D-89F6-F6A3F1962D51}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|x86 = Debug|x86 9 | Release|x86 = Release|x86 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {43091E5B-7AFC-4D2D-89F6-F6A3F1962D51}.Debug|x86.ActiveCfg = Debug|x86 13 | {43091E5B-7AFC-4D2D-89F6-F6A3F1962D51}.Debug|x86.Build.0 = Debug|x86 14 | {43091E5B-7AFC-4D2D-89F6-F6A3F1962D51}.Release|x86.ActiveCfg = Release|x86 15 | {43091E5B-7AFC-4D2D-89F6-F6A3F1962D51}.Release|x86.Build.0 = Release|x86 16 | EndGlobalSection 17 | EndGlobal 18 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/Bootstrapper.cs: -------------------------------------------------------------------------------- 1 | using Nancy; 2 | 3 | namespace QueryHandler 4 | { 5 | public class Bootstrapper : DefaultNancyBootstrapper 6 | { 7 | protected override void ConfigureApplicationContainer(Nancy.TinyIoc.TinyIoCContainer container) 8 | { 9 | var mediator = new Mediator(); 10 | 11 | // mediator.Register, User>>(delegate 12 | // { 13 | // return new UserQueryHandler(); 14 | // }); 15 | // 16 | // mediator.Register, int>>(delegate 17 | // { 18 | // return new InsertUserCommandHandler(); 19 | // } 20 | // ); 21 | 22 | mediator.Register,User>, UserQueryHandler>(); 23 | 24 | mediator.Register,int>,UpdateUserCommandHandler>(); 25 | mediator.Register,int>,InsertUserCommandHandler>(); 26 | mediator.Register,int>,DeleteUserCommandHandler>(); 27 | 28 | // mediator.Register, int>>(delegate 29 | // { 30 | // return new UpdateUserCommandHandler(); 31 | // } 32 | // ); 33 | 34 | container.Register(mediator); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/CommandAndQueryHandlers.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | x86 6 | {43091E5B-7AFC-4D2D-89F6-F6A3F1962D51} 7 | Exe 8 | QueryHandler 9 | QueryHandler 10 | v4.5 11 | 12 | 13 | true 14 | full 15 | false 16 | bin\Debug 17 | DEBUG; 18 | prompt 19 | 4 20 | true 21 | x86 22 | 23 | 24 | full 25 | true 26 | bin\Release 27 | prompt 28 | 4 29 | true 30 | x86 31 | 32 | 33 | 34 | 35 | ..\packages\Owin.1.0\lib\net40\Owin.dll 36 | 37 | 38 | ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll 39 | 40 | 41 | ..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll 42 | 43 | 44 | ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll 45 | 46 | 47 | ..\packages\Nancy.1.3.0\lib\net40\Nancy.dll 48 | 49 | 50 | ..\packages\Nancy.Owin.1.3.0\lib\net40\Nancy.Owin.dll 51 | 52 | 53 | 54 | ..\packages\FluentValidation.5.6.2.0\lib\Net45\FluentValidation.dll 55 | 56 | 57 | ..\packages\Nancy.Validation.FluentValidation.1.3.0\lib\net40\Nancy.Validation.FluentValidation.dll 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/DB.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace QueryHandler 4 | { 5 | public static class DB 6 | { 7 | //Our DB 8 | public static List Data = new List() 9 | { 10 | new User{ Id = 1, FirstName = "Jim", LastName = "Parsons", EmailAddress = "jim@parsons.com" }, 11 | new User{ Id = 2, FirstName = "Fred", LastName = "Smith", EmailAddress = "fred@smith.com" }, 12 | new User{ Id = 3, FirstName = "Bob", LastName = "Hope", EmailAddress = "bob@hope.com" }, 13 | new User{ Id = 4, FirstName = "Bernard", LastName = "Targarian", EmailAddress = "bernard@targarian.com" }, 14 | new User{ Id = 5, FirstName = "Troy", LastName = "Vega", EmailAddress = "troy@vega.com" } 15 | }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/DeleteUserCommand.cs: -------------------------------------------------------------------------------- 1 | namespace QueryHandler 2 | { 3 | public class DeleteUserCommand : ICommand 4 | { 5 | public int UserId { get; set; } 6 | 7 | public DeleteUserCommand(int userId) 8 | { 9 | this.UserId = userId; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/DeleteUserCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace QueryHandler 5 | { 6 | public class DeleteUserCommandHandler : ICommandHandler, int> 7 | { 8 | public int Handle(ICommand command) 9 | { 10 | var deleteUserCommand = command as DeleteUserCommand; 11 | 12 | var user = DB.Data.FirstOrDefault(x => x.Id == deleteUserCommand.UserId); 13 | 14 | if(user == null) 15 | { 16 | throw new InvalidOperationException("User not found"); 17 | } 18 | 19 | var removed = DB.Data.Remove(user); 20 | return removed ? 1 : 0; 21 | } 22 | 23 | public bool CanHandle(Type commandType) 24 | { 25 | return commandType == typeof(DeleteUserCommand); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/ICommand.cs: -------------------------------------------------------------------------------- 1 | namespace QueryHandler 2 | { 3 | public interface ICommand 4 | { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/ICommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace QueryHandler 4 | { 5 | public interface ICommandHandler 6 | where TCommand : ICommand 7 | { 8 | TResponse Handle(TCommand command); 9 | 10 | bool CanHandle(Type commandType); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/IMediate.cs: -------------------------------------------------------------------------------- 1 | namespace QueryHandler 2 | { 3 | public interface IMediate 4 | { 5 | TResponse Request(IQuery query); 6 | 7 | TResponse Send(ICommand cmd); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/IQuery.cs: -------------------------------------------------------------------------------- 1 | namespace QueryHandler 2 | { 3 | public interface IQuery 4 | { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/IQueryHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace QueryHandler 4 | { 5 | public interface IQueryHandler 6 | where TQuery : IQuery 7 | { 8 | TResponse Handle(TQuery query); 9 | 10 | bool CanHandle(Type commandType); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/InsertUserCommand.cs: -------------------------------------------------------------------------------- 1 | namespace QueryHandler 2 | { 3 | public class InsertUserCommand : ICommand 4 | { 5 | public User User { get; private set; } 6 | 7 | public InsertUserCommand(User user) 8 | { 9 | this.User = user; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/InsertUserCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using FluentValidation; 5 | using FluentValidation.Results; 6 | 7 | namespace QueryHandler 8 | { 9 | public class InsertUserCommandHandler : ICommandHandler,int> 10 | { 11 | public int Handle(ICommand command) 12 | { 13 | var insertUserCmd = command as InsertUserCommand; 14 | 15 | var errorList = new Dictionary(); 16 | 17 | //Validation - put this here to avoid having code check ModelValidationResult in module. Just a personal preference 18 | var validator = new UserValidator(); 19 | validator.ValidateAndThrow(insertUserCmd.User); 20 | 21 | //Basic business logic 22 | var existingPerson = DB.Data.FirstOrDefault(x => x.EmailAddress == insertUserCmd.User.EmailAddress); 23 | 24 | if (existingPerson != null) 25 | { 26 | errorList.Add("EmailAddress", "User already exists"); 27 | } 28 | 29 | if (errorList.Any()) 30 | { 31 | var validationErrors = new List(); 32 | foreach (var item in errorList) 33 | { 34 | validationErrors.Add(new ValidationFailure(item.Key, item.Value)); 35 | } 36 | throw new ValidationException(validationErrors); 37 | } 38 | 39 | //Other business logic that might do checks and return errors 40 | 41 | var newid = DB.Data.Last().Id + 1; 42 | insertUserCmd.User.Id = newid; 43 | DB.Data.Add(insertUserCmd.User); 44 | 45 | return newid; 46 | } 47 | 48 | public bool CanHandle(Type commandType) 49 | { 50 | return commandType == typeof(InsertUserCommand); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/Mediator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace QueryHandler 6 | { 7 | public class Mediator : IMediate 8 | { 9 | public delegate object Creator(Mediator container); 10 | 11 | private readonly Dictionary _typeToCreator = new Dictionary(); 12 | 13 | public void Register(Creator creator) 14 | { 15 | _typeToCreator.Add(typeof(T), creator); 16 | } 17 | 18 | private T Create() 19 | { 20 | return (T)_typeToCreator[typeof(T)](this); 21 | } 22 | 23 | 24 | public TResponse Request(IQuery query) 25 | { 26 | var handler = Resolve,TResponse>>(query.GetType()); 27 | return handler.Handle(query); 28 | } 29 | 30 | public TResponse Send(ICommand cmd) 31 | { 32 | var handler = Resolve, TResponse>>(cmd.GetType()); 33 | return handler.Handle(cmd); 34 | } 35 | 36 | public T Resolve(Type instance) 37 | { 38 | T item = default(T); 39 | 40 | foreach (var registration in Classes) 41 | { 42 | try 43 | { 44 | var instansiatedObject = (T)Activator.CreateInstance(registration.ConcreteType); 45 | 46 | var mi = typeof(T).GetMethod("CanHandle"); 47 | 48 | var canHandle = (bool)mi.Invoke(instansiatedObject, new object[] { instance }); 49 | if (canHandle) 50 | { 51 | item = instansiatedObject; 52 | break; 53 | } 54 | } 55 | catch 56 | { 57 | } 58 | } 59 | 60 | 61 | return item; 62 | } 63 | 64 | 65 | public void Register() 66 | { 67 | Classes.Add(new Registration{ InterfaceType = typeof(TInterface), ConcreteType = typeof(TImplementation) }); 68 | } 69 | 70 | private readonly List Classes = new List(); 71 | 72 | public class Registration 73 | { 74 | public Type InterfaceType { get; set; } 75 | 76 | public Type ConcreteType { get; set; } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Owin.Hosting; 3 | 4 | namespace QueryHandler 5 | { 6 | class MainClass 7 | { 8 | public static void Main(string[] args) 9 | { 10 | using (WebApp.Start("http://+:5678")) 11 | { 12 | Console.WriteLine("Running on http://localhost:5678"); 13 | Console.WriteLine("Press enter to exit"); 14 | Console.ReadLine(); 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | 7 | [assembly: AssemblyTitle("QueryHandler")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("")] 12 | [assembly: AssemblyCopyright("jonathan")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 17 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 18 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 19 | 20 | [assembly: AssemblyVersion("1.0.*")] 21 | 22 | // The following attributes are used to specify the signing key for the assembly, 23 | // if desired. See the Mono documentation for more information about signing. 24 | 25 | //[assembly: AssemblyDelaySign(false)] 26 | //[assembly: AssemblyKeyFile("")] 27 | 28 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/Startup.cs: -------------------------------------------------------------------------------- 1 | using Owin; 2 | 3 | namespace QueryHandler 4 | { 5 | public class Startup 6 | { 7 | public void Configuration(IAppBuilder app) 8 | { 9 | app.UseNancy(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/UpdateUserCommand.cs: -------------------------------------------------------------------------------- 1 | namespace QueryHandler 2 | { 3 | public class UpdateUserCommand : ICommand 4 | { 5 | public User User { get; private set; } 6 | 7 | public UpdateUserCommand(User user) 8 | { 9 | this.User = user; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/UpdateUserCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using System; 3 | using System.Linq; 4 | 5 | namespace QueryHandler 6 | { 7 | public class UpdateUserCommandHandler : ICommandHandler, int> 8 | { 9 | public int Handle(ICommand command) 10 | { 11 | var updateUserCmd = command as UpdateUserCommand; 12 | 13 | //Validation 14 | var validator = new UserValidator(); 15 | validator.ValidateAndThrow(updateUserCmd.User); 16 | 17 | var currentUser = DB.Data.FirstOrDefault(x => x.Id == updateUserCmd.User.Id); 18 | 19 | //Idempotent!! 20 | if (currentUser == null) 21 | { 22 | throw new InvalidOperationException("User not found"); 23 | } 24 | 25 | currentUser.FirstName = updateUserCmd.User.FirstName; 26 | currentUser.LastName = updateUserCmd.User.LastName; 27 | currentUser.EmailAddress = updateUserCmd.User.EmailAddress; 28 | 29 | //Rows affected 30 | return 1; 31 | } 32 | 33 | public bool CanHandle(Type commandType) 34 | { 35 | return commandType == typeof(UpdateUserCommand); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/User.cs: -------------------------------------------------------------------------------- 1 | namespace QueryHandler 2 | { 3 | public class User 4 | { 5 | public int Id { get; set; } 6 | 7 | public string FirstName{ get; set; } 8 | 9 | public string LastName{ get; set; } 10 | 11 | public string FullName{ get { return FirstName + " " + LastName; } } 12 | 13 | public string EmailAddress { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/UserModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Nancy; 4 | using Nancy.ModelBinding; 5 | using FluentValidation; 6 | 7 | namespace QueryHandler 8 | { 9 | public class UserModule : NancyModule 10 | { 11 | 12 | public UserModule(IMediate mediator) 13 | { 14 | Get["/"] = _ => "Hi Earth People!"; 15 | 16 | Get["/{id:int}"] = parameters => 17 | { 18 | var userQuery = new UserQuery((int)parameters.id); 19 | try 20 | { 21 | var person = mediator.Request(userQuery); 22 | return person; 23 | } 24 | catch (InvalidOperationException) 25 | { 26 | return HttpStatusCode.NotFound; 27 | } 28 | }; 29 | 30 | Put["/{id:int}"] = _ => 31 | { 32 | var user = this.Bind(); 33 | var updateUserCmd = new UpdateUserCommand(user); 34 | try 35 | { 36 | mediator.Send(updateUserCmd); 37 | return Negotiate.WithStatusCode(HttpStatusCode.NoContent); 38 | } 39 | catch (ValidationException ex) 40 | { 41 | return Negotiate.WithModel(ex.Errors.Select(x => new{x.PropertyName, x.ErrorMessage})).WithStatusCode(HttpStatusCode.UnprocessableEntity); 42 | } 43 | catch (InvalidOperationException) 44 | { 45 | return HttpStatusCode.NotFound; 46 | } 47 | }; 48 | 49 | Post["/"] = _ => 50 | { 51 | var user = this.Bind(); 52 | var insertUserCmd = new InsertUserCommand(user); 53 | try 54 | { 55 | var id = mediator.Send(insertUserCmd); 56 | return Negotiate.WithStatusCode(HttpStatusCode.Created).WithHeader("Location", Context.Request.Url + "/" + id); 57 | } 58 | catch (ValidationException ex) 59 | { 60 | return Negotiate.WithModel(ex.Errors.Select(x => new{x.PropertyName, x.ErrorMessage})).WithStatusCode(HttpStatusCode.UnprocessableEntity); 61 | } 62 | }; 63 | 64 | Delete["/{id:int}"] = parameters => 65 | { 66 | var deleteUserCommand = new DeleteUserCommand((int)parameters.id); 67 | try 68 | { 69 | mediator.Send(deleteUserCommand); 70 | } 71 | catch (InvalidOperationException) 72 | { 73 | return HttpStatusCode.NotFound; 74 | } 75 | return HttpStatusCode.NoContent; 76 | }; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/UserQuery.cs: -------------------------------------------------------------------------------- 1 | namespace QueryHandler 2 | { 3 | public class UserQuery : IQuery 4 | { 5 | public int UserId { get; private set; } 6 | 7 | public UserQuery(int userId) 8 | { 9 | UserId = userId; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/UserQueryHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System; 3 | 4 | namespace QueryHandler 5 | { 6 | public class UserQueryHandler : IQueryHandler, User> 7 | { 8 | public User Handle(IQuery query) 9 | { 10 | var userQuery = query as UserQuery; 11 | var user = DB.Data.FirstOrDefault(x => x.Id == userQuery.UserId); 12 | 13 | if (user == null) 14 | { 15 | throw new InvalidOperationException("User not found"); 16 | } 17 | 18 | return user; 19 | } 20 | 21 | public bool CanHandle(Type queryType) 22 | { 23 | return queryType == typeof(UserQuery); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/UserValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace QueryHandler 4 | { 5 | public class UserValidator : AbstractValidator 6 | { 7 | public UserValidator() 8 | { 9 | this.RuleFor(x => x.EmailAddress).NotEmpty(); 10 | this.RuleFor(x => x.FirstName).NotEmpty(); 11 | this.RuleFor(x => x.LastName).NotEmpty(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/CommandAndQueryHandlers/CommandAndQueryHandlers/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/DbConnectionAndCommands/DbConnectionAndCommands.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbConnectionAndCommands", "DbConnectionAndCommands\DbConnectionAndCommands.csproj", "{B0853513-9F58-4FB1-9B99-F8F3FBD67F93}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {B0853513-9F58-4FB1-9B99-F8F3FBD67F93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {B0853513-9F58-4FB1-9B99-F8F3FBD67F93}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {B0853513-9F58-4FB1-9B99-F8F3FBD67F93}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {B0853513-9F58-4FB1-9B99-F8F3FBD67F93}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/DbConnectionAndCommands/DbConnectionAndCommands/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/DbConnectionAndCommands/DbConnectionAndCommands/Bootstrapper.cs: -------------------------------------------------------------------------------- 1 | namespace DbConnectionAndCommands 2 | { 3 | using Nancy; 4 | using Nancy.TinyIoc; 5 | 6 | public class Bootstrapper : DefaultNancyBootstrapper 7 | { 8 | protected override void ConfigureApplicationContainer(TinyIoCContainer container) 9 | { 10 | base.ConfigureApplicationContainer(container); 11 | 12 | container.Register(); 13 | 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/DbConnectionAndCommands/DbConnectionAndCommands/DbConnectionAndCommands.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {B0853513-9F58-4FB1-9B99-F8F3FBD67F93} 8 | Exe 9 | Properties 10 | DbConnectionAndCommands 11 | DbConnectionAndCommands 12 | v4.5.1 13 | 512 14 | true 15 | ..\ 16 | true 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | ..\packages\Dapper.1.42\lib\net45\Dapper.dll 40 | True 41 | 42 | 43 | ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll 44 | 45 | 46 | ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll 47 | 48 | 49 | ..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll 50 | 51 | 52 | ..\packages\Nancy.1.3.0\lib\net40\Nancy.dll 53 | 54 | 55 | ..\packages\Nancy.Owin.1.3.0\lib\net40\Nancy.Owin.dll 56 | 57 | 58 | ..\packages\Owin.1.0\lib\net40\Owin.dll 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 88 | -------------------------------------------------------------------------------- /src/DbConnectionAndCommands/DbConnectionAndCommands/HomeModule.cs: -------------------------------------------------------------------------------- 1 | namespace DbConnectionAndCommands 2 | { 3 | using Nancy; 4 | using Dapper; 5 | 6 | public class HomeModule : NancyModule 7 | { 8 | public HomeModule(IDbConnectionProvider connectionProvider) 9 | { 10 | Get["/"] = _ => 11 | { 12 | using (var conn = connectionProvider.GetConnection()) 13 | { 14 | return conn.Query("select * from users"); 15 | } 16 | }; 17 | 18 | Get["/complicatedquery"] = _ => 19 | { 20 | //What if we need to build up a big view model and need to execute a couple of queries? 21 | //We could create some methods in this class and separate those queries out and build the view model up. 22 | //What if we have 3-4 endpoints that need to do the same? We then have a large module class which in my 23 | //opinion should be anaemic and have its single responsibility be for return http specific things. 24 | 25 | return 200; 26 | }; 27 | 28 | Post["/"] = _ => 29 | { 30 | //So we could inject a dbconnection and return data from this module but I dont like that due to the comments above. 31 | //What about POST/PUT? We need somewhere to do business logic etc which I think should be a service or command layer 32 | return 201; 33 | }; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/DbConnectionAndCommands/DbConnectionAndCommands/IDbConnectionProvider.cs: -------------------------------------------------------------------------------- 1 | namespace DbConnectionAndCommands 2 | { 3 | using System.Data; 4 | 5 | public interface IDbConnectionProvider 6 | { 7 | IDbConnection GetConnection(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/DbConnectionAndCommands/DbConnectionAndCommands/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Owin.Hosting; 3 | 4 | namespace DbConnectionAndCommands 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | using (WebApp.Start("http://+:5678")) 11 | { 12 | Console.WriteLine("Running on http://localhost:5678"); 13 | Console.WriteLine("Press enter to exit"); 14 | Console.ReadLine(); 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/DbConnectionAndCommands/DbConnectionAndCommands/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("DbConnectionAndCommands")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("DbConnectionAndCommands")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("336e0e4f-8ee8-4296-9cf0-d712476c2586")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/DbConnectionAndCommands/DbConnectionAndCommands/SqlServerConnectionProvider.cs: -------------------------------------------------------------------------------- 1 | namespace DbConnectionAndCommands 2 | { 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Data.SqlClient; 6 | 7 | /// 8 | /// Implement the IDbConnectionProvider interface. 9 | /// 10 | public class SqlServerConnectionProvider : IDbConnectionProvider 11 | { 12 | private readonly string connectionString; 13 | 14 | public SqlServerConnectionProvider() 15 | { 16 | this.connectionString = ConfigurationManager.ConnectionStrings["MyDb"].ConnectionString; 17 | } 18 | 19 | /// 20 | /// Return an open SqlConnection to the VQ database. 21 | /// 22 | /// 23 | public IDbConnection GetConnection() 24 | { 25 | var connection = new SqlConnection(this.connectionString); 26 | connection.Open(); 27 | return connection; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/DbConnectionAndCommands/DbConnectionAndCommands/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace DbConnectionAndCommands 2 | { 3 | using Owin; 4 | 5 | public class Startup 6 | { 7 | public void Configuration(IAppBuilder app) 8 | { 9 | app.UseNancy(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/DbConnectionAndCommands/DbConnectionAndCommands/User.cs: -------------------------------------------------------------------------------- 1 | namespace DbConnectionAndCommands 2 | { 3 | public class User 4 | { 5 | public int Id { get; set; } 6 | public string FirstName { get; set; } 7 | public string LastName { get; set; } 8 | public string Email { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /src/DbConnectionAndCommands/DbConnectionAndCommands/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/DbConnectionAndCommands/db.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchannon/LayerAllTheThings/2c34faf32d8892d5b3fc08f2c1abdd49357db56f/src/DbConnectionAndCommands/db.sql -------------------------------------------------------------------------------- /src/MediatR/MediatR.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediatR", "MediatR\MediatR.csproj", "{43091E5B-7AFC-4D2D-89F6-F6A3F1962D51}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x86 = Debug|x86 11 | Release|x86 = Release|x86 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {43091E5B-7AFC-4D2D-89F6-F6A3F1962D51}.Debug|x86.ActiveCfg = Debug|x86 15 | {43091E5B-7AFC-4D2D-89F6-F6A3F1962D51}.Debug|x86.Build.0 = Debug|x86 16 | {43091E5B-7AFC-4D2D-89F6-F6A3F1962D51}.Release|x86.ActiveCfg = Release|x86 17 | {43091E5B-7AFC-4D2D-89F6-F6A3F1962D51}.Release|x86.Build.0 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/MediatR/MediatR/Commands.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using MediatR; 4 | 5 | namespace QueryHandler 6 | { 7 | 8 | public class Envelope : IRequest 9 | { 10 | public DateTime Created { get; private set; } 11 | public Guid CommandId { get; private set; } 12 | public T Command { get; private set; } 13 | 14 | public Envelope(Guid commandId, T command) 15 | { 16 | CommandId = commandId; 17 | Created = DateTime.UtcNow; 18 | Command = command; 19 | } 20 | } 21 | 22 | public class SellInventory 23 | { 24 | public int Id { get; set; } 25 | public int Quantity { get; set; } 26 | 27 | public SellInventory() { } 28 | 29 | public SellInventory(int id, int quantity) 30 | { 31 | Quantity = quantity; 32 | Id = Id; 33 | } 34 | } 35 | 36 | public class SellInventoryHandler : IRequestHandler, Unit> 37 | { 38 | private readonly InMemoryDatabase _db; 39 | 40 | public SellInventoryHandler(InMemoryDatabase db) 41 | { 42 | _db = db; 43 | } 44 | 45 | public Unit Handle(Envelope message) 46 | { 47 | // Skip any commands already processed 48 | if (_db.Idempotent.Any(x => x == message.CommandId)) return new Unit(); 49 | 50 | var item = (from x in _db.Inventory where x.Id == message.Command.Id select x).SingleOrDefault(); 51 | if (item == null) throw new InvalidOperationException("Inventory item not found."); 52 | 53 | item.SellInventory(message.Command.Quantity); 54 | 55 | _db.Idempotent.Add(message.CommandId); 56 | 57 | return new Unit(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/MediatR/MediatR/MediatR.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | {43091E5B-7AFC-4D2D-89F6-F6A3F1962D51} 7 | Exe 8 | QueryHandler 9 | QueryHandler 10 | v4.5 11 | 12 | 13 | true 14 | full 15 | false 16 | bin\Debug 17 | DEBUG; 18 | prompt 19 | 4 20 | true 21 | x86 22 | 23 | 24 | full 25 | true 26 | bin\Release 27 | prompt 28 | 4 29 | true 30 | x86 31 | 32 | 33 | 34 | ..\packages\MediatR.2.0.2\lib\net45\MediatR.dll 35 | True 36 | 37 | 38 | ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll 39 | True 40 | 41 | 42 | ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll 43 | True 44 | 45 | 46 | ..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll 47 | True 48 | 49 | 50 | ..\packages\Nancy.1.3.0\lib\net40\Nancy.dll 51 | True 52 | 53 | 54 | ..\packages\Nancy.Owin.1.3.0\lib\net40\Nancy.Owin.dll 55 | True 56 | 57 | 58 | ..\packages\Owin.1.0\lib\net40\Owin.dll 59 | True 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/MediatR/MediatR/NancyExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Nancy; 3 | using Nancy.ModelBinding; 4 | 5 | namespace QueryHandler 6 | { 7 | public static class NancyExtensions 8 | { 9 | public static Envelope BindCommandEnvelope(this NancyModule module, Guid commandId) 10 | { 11 | return new Envelope(commandId, module.Bind()); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/MediatR/MediatR/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using MediatR; 4 | using Microsoft.Owin.Hosting; 5 | using Nancy; 6 | using Owin; 7 | 8 | namespace QueryHandler 9 | { 10 | class MainClass 11 | { 12 | public static void Main(string[] args) 13 | { 14 | using (WebApp.Start("http://+:5678")) 15 | { 16 | Console.WriteLine("Running on http://localhost:5678"); 17 | Console.WriteLine("Press enter to exit"); 18 | Console.ReadLine(); 19 | } 20 | } 21 | } 22 | 23 | public class Startup 24 | { 25 | public void Configuration(IAppBuilder app) 26 | { 27 | app.UseNancy(); 28 | } 29 | } 30 | 31 | public class Bootstrapper : DefaultNancyBootstrapper 32 | { 33 | protected override void ConfigureApplicationContainer(Nancy.TinyIoc.TinyIoCContainer container) 34 | { 35 | container.Register().AsSingleton(); 36 | container.Register(new Mediator(container.Resolve, container.ResolveAll)); 37 | container.Register, GetInventoryItemHandler>(); 38 | container.Register, Unit>, SellInventoryHandler>(); 39 | } 40 | } 41 | 42 | public class PersonModule : NancyModule 43 | { 44 | public PersonModule(IMediator mediator) 45 | { 46 | Get["/"] = _ => "Hi Earth People!"; 47 | 48 | Get["/{id:int}"] = parameters => 49 | { 50 | var query = new GetInventoryItem((int)parameters.id); 51 | var item = mediator.Send(query); 52 | return item; 53 | }; 54 | 55 | Put["/sell/{commandId:guid}"] = parameters => 56 | { 57 | var message = this.BindCommandEnvelope((Guid)parameters.commandId); 58 | mediator.Send(message); 59 | return HttpStatusCode.NoContent; 60 | }; 61 | 62 | } 63 | } 64 | 65 | public class InMemoryDatabase 66 | { 67 | public IList Inventory = new List 68 | { 69 | new InventoryItem(1, "Taylormade M1 460", 599.99m, 5), 70 | new InventoryItem(2, "Titleist 945D2", 499.99m, 8), 71 | new InventoryItem(3, "Callaway Golf XR", 299.99m, 3), 72 | new InventoryItem(4, "Nike Vapor", 329.99m, 1), 73 | new InventoryItem(5, "Cobra Fly Z+", 399.99m, 2), 74 | }; 75 | 76 | public IList Idempotent = new List(); 77 | } 78 | 79 | public class InventoryItem 80 | { 81 | public int Id { get; set; } 82 | public string Name{ get; set; } 83 | public decimal Price { get; set; } 84 | public int QuantityOnHand { get; set; } 85 | 86 | public InventoryItem(int id, string name, decimal price, int quantityOnHand) 87 | { 88 | Id = id; 89 | Name = name; 90 | Price = price; 91 | QuantityOnHand = quantityOnHand; 92 | } 93 | 94 | public void SellInventory(int quantity) 95 | { 96 | if ((QuantityOnHand - quantity) < 0) 97 | { 98 | throw new InvalidOperationException("Cannot sell more than on hand."); 99 | } 100 | 101 | QuantityOnHand -= quantity; 102 | } 103 | } 104 | 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/MediatR/MediatR/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | 7 | [assembly: AssemblyTitle("QueryHandler")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("")] 12 | [assembly: AssemblyCopyright("jonathan")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 17 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 18 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 19 | 20 | [assembly: AssemblyVersion("1.0.*")] 21 | 22 | // The following attributes are used to specify the signing key for the assembly, 23 | // if desired. See the Mono documentation for more information about signing. 24 | 25 | //[assembly: AssemblyDelaySign(false)] 26 | //[assembly: AssemblyKeyFile("")] 27 | 28 | -------------------------------------------------------------------------------- /src/MediatR/MediatR/Queries.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using MediatR; 3 | 4 | namespace QueryHandler 5 | { 6 | public class GetInventoryItem : IRequest 7 | { 8 | public int Id { get; private set; } 9 | 10 | public GetInventoryItem(int id) 11 | { 12 | Id = id; 13 | } 14 | } 15 | 16 | public class GetInventoryItemHandler : IRequestHandler 17 | { 18 | private readonly InMemoryDatabase _db; 19 | 20 | public GetInventoryItemHandler(InMemoryDatabase db) 21 | { 22 | _db = db; 23 | } 24 | 25 | public InventoryItem Handle(GetInventoryItem message) 26 | { 27 | return _db.Inventory.FirstOrDefault(x => x.Id == message.Id); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/MediatR/MediatR/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/MultiDbSupport/MultiDbSupport.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultiDbSupport", "MultiDbSupport\MultiDbSupport.csproj", "{ECFD6863-627E-42ED-9B3E-64D72F5C5D28}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {ECFD6863-627E-42ED-9B3E-64D72F5C5D28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {ECFD6863-627E-42ED-9B3E-64D72F5C5D28}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {ECFD6863-627E-42ED-9B3E-64D72F5C5D28}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {ECFD6863-627E-42ED-9B3E-64D72F5C5D28}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/MultiDbSupport/MultiDbSupport/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/MultiDbSupport/MultiDbSupport/Bootstrapper.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupport 2 | { 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Configuration; 7 | 8 | using MediatR; 9 | 10 | using Nancy; 11 | using Nancy.TinyIoc; 12 | 13 | public class Bootstrapper : DefaultNancyBootstrapper 14 | { 15 | protected override void ConfigureApplicationContainer(TinyIoCContainer container) 16 | { 17 | //base.ConfigureApplicationContainer(container); // Lets our app do all the wiring up 18 | 19 | switch (ConfigurationManager.ConnectionStrings["mydb"].ProviderName.ToLower()) 20 | { 21 | case "system.data.sqlclient": 22 | container.Register(); 23 | break; 24 | case "npgsql": 25 | container.Register(); 26 | break; 27 | default: 28 | throw new ArgumentException("Invalid ProviderName in connection string."); 29 | } 30 | 31 | container.Register(new Mediator(container.Resolve, container.ResolveAll)); 32 | container.Register>, UserListQueryRequestHandler>(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/MultiDbSupport/MultiDbSupport/HomeModule.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupport 2 | { 3 | using MediatR; 4 | 5 | using Nancy; 6 | 7 | public class HomeModule : NancyModule 8 | { 9 | public HomeModule(IMediator mediator) 10 | { 11 | Get["/"] = _ => 12 | { 13 | var query = new UserListQuery(-1); 14 | 15 | return mediator.Send(query); 16 | }; 17 | 18 | Post["/"] = _ => 19 | { 20 | //We would do the same above using mediatr to call a command handler 21 | return 201; 22 | }; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/MultiDbSupport/MultiDbSupport/IDbConnectionProvider.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupport 2 | { 3 | using System.Data; 4 | 5 | public interface IDbConnectionProvider 6 | { 7 | IDbConnection GetConnection(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/MultiDbSupport/MultiDbSupport/MultiDbSupport.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {ECFD6863-627E-42ED-9B3E-64D72F5C5D28} 8 | Exe 9 | Properties 10 | MultiDbSupport 11 | MultiDbSupport 12 | v4.5.1 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\Dapper.1.42\lib\net45\Dapper.dll 38 | 39 | 40 | ..\packages\MediatR.2.0.2\lib\net45\MediatR.dll 41 | True 42 | 43 | 44 | ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll 45 | 46 | 47 | ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll 48 | 49 | 50 | ..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll 51 | 52 | 53 | ..\packages\Nancy.1.3.0\lib\net40\Nancy.dll 54 | 55 | 56 | ..\packages\Nancy.Owin.1.3.0\lib\net40\Nancy.Owin.dll 57 | 58 | 59 | ..\packages\Npgsql.3.0.4\lib\net45\Npgsql.dll 60 | True 61 | 62 | 63 | ..\packages\Owin.1.0\lib\net40\Owin.dll 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 96 | -------------------------------------------------------------------------------- /src/MultiDbSupport/MultiDbSupport/PostgresConnectionProvider.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupport 2 | { 3 | using System.Configuration; 4 | using System.Data; 5 | 6 | using Npgsql; 7 | 8 | public class PostgresConnectionProvider : IDbConnectionProvider 9 | { 10 | private readonly string connectionString; 11 | 12 | public PostgresConnectionProvider() 13 | { 14 | this.connectionString = ConfigurationManager.ConnectionStrings["mydb"].ConnectionString; 15 | } 16 | 17 | public IDbConnection GetConnection() 18 | { 19 | var connection = new NpgsqlConnection(this.connectionString); 20 | connection.Open(); 21 | return connection; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/MultiDbSupport/MultiDbSupport/Program.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupport 2 | { 3 | using System; 4 | 5 | using Microsoft.Owin.Hosting; 6 | 7 | class Program 8 | { 9 | static void Main(string[] args) 10 | { 11 | using (WebApp.Start("http://+:5678")) 12 | { 13 | Console.WriteLine("Running on http://localhost:5678"); 14 | Console.WriteLine("Press enter to exit"); 15 | Console.ReadLine(); 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/MultiDbSupport/MultiDbSupport/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("MultiDbSupport")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("MultiDbSupport")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("2ef1430c-20b6-4d8b-ae4a-525f4ee66716")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/MultiDbSupport/MultiDbSupport/SqlServerConnectionProvider.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupport 2 | { 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Data.SqlClient; 6 | 7 | /// 8 | /// Implement the IDbConnectionProvider interface. 9 | /// 10 | public class SqlServerConnectionProvider : IDbConnectionProvider 11 | { 12 | private readonly string connectionString; 13 | 14 | public SqlServerConnectionProvider() 15 | { 16 | this.connectionString = ConfigurationManager.ConnectionStrings["MyDb"].ConnectionString; 17 | } 18 | 19 | /// 20 | /// Return an open SqlConnection to the VQ database. 21 | /// 22 | /// 23 | public IDbConnection GetConnection() 24 | { 25 | var connection = new SqlConnection(this.connectionString); 26 | connection.Open(); 27 | return connection; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/MultiDbSupport/MultiDbSupport/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupport 2 | { 3 | using Owin; 4 | 5 | public class Startup 6 | { 7 | public void Configuration(IAppBuilder app) 8 | { 9 | app.UseNancy(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/MultiDbSupport/MultiDbSupport/User.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupport 2 | { 3 | public class User 4 | { 5 | public int Id { get; set; } 6 | public string FirstName { get; set; } 7 | public string LastName { get; set; } 8 | public string Email { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /src/MultiDbSupport/MultiDbSupport/UserListQuery.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupport 2 | { 3 | using System.Collections.Generic; 4 | 5 | using MediatR; 6 | 7 | public class UserListQuery : IRequest> 8 | { 9 | public UserListQuery(int customerId) 10 | { 11 | this.CustomerId = customerId; 12 | } 13 | 14 | //Just an example of having parameters for queries 15 | public int CustomerId { get; private set; } 16 | } 17 | } -------------------------------------------------------------------------------- /src/MultiDbSupport/MultiDbSupport/UserListQueryRequestHandler.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupport 2 | { 3 | using System.Collections.Generic; 4 | using System.Data; 5 | 6 | using Dapper; 7 | 8 | using MediatR; 9 | 10 | public class UserListQueryRequestHandler : IRequestHandler> 11 | { 12 | private readonly IDbConnectionProvider dbConnectionProvider; 13 | 14 | public UserListQueryRequestHandler(IDbConnectionProvider dbConnectionProvider) 15 | { 16 | this.dbConnectionProvider = dbConnectionProvider; 17 | } 18 | 19 | public IEnumerable Handle(UserListQuery message) 20 | { 21 | //We need to support multi datbases so when we want to query the database for our users what db specific SQL should we use? 22 | //One option is to put the SQL in a resx file but then we'll need to do a if/then/else check in all our query classes 23 | //to work out which sql to use, not pretty. 24 | //Another option would be to move the abstraction to the database and use stored procedures! That way we can have one query class 25 | //and pass any parameters into the stored procedure and that can do whatever it needs to do. 26 | 27 | using (var dbConnection = this.dbConnectionProvider.GetConnection()) 28 | { 29 | var data = dbConnection.Query("spGetUsers", commandType: CommandType.StoredProcedure); 30 | return data; 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/MultiDbSupport/MultiDbSupport/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/MultiDbSupport/db.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchannon/LayerAllTheThings/2c34faf32d8892d5b3fc08f2c1abdd49357db56f/src/MultiDbSupport/db.sql -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions.Tests/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions.Tests/Features/Users/AddUser/AddUserTests.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupportWithConventions.Tests.Features.Users.AddUser 2 | { 3 | using System; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | 7 | using Microsoft.Owin.Builder; 8 | 9 | using MultiDbSupportWithConventions.Features.Users; 10 | 11 | using Xunit; 12 | 13 | public class AddUserTests 14 | { 15 | [Fact] 16 | public async Task Should_contain_location_header_and_201() 17 | { 18 | var appBuilder = new AppBuilder(); 19 | new Startup(new OurTestBStrapper(new NoDbAddUserCommandHandler(userExists: false, newUserId: 1))) 20 | .Configuration(appBuilder); 21 | 22 | var handler = this.GetOwinHttpMessageHandler(appBuilder); 23 | 24 | var client = this.GetHttpClient(handler); 25 | 26 | var response = await client.PostAsJsonAsync("/", new AddUserCommand 27 | { 28 | FirstName = "vincent", 29 | LastName = "vega", 30 | Email = "vincent@home.com" 31 | }); 32 | 33 | Assert.Equal(201, (int)response.StatusCode); 34 | Assert.NotNull(response.Headers.Location); 35 | } 36 | 37 | [Fact] 38 | public async Task Should_return_422_on_invalid_data() 39 | { 40 | var appBuilder = new AppBuilder(); 41 | new Startup(new OurTestBStrapper(new NoDbAddUserCommandHandler(userExists: false, newUserId: 1))) 42 | .Configuration(appBuilder); 43 | 44 | var handler = this.GetOwinHttpMessageHandler(appBuilder); 45 | 46 | var client = this.GetHttpClient(handler); 47 | 48 | var response = await client.PostAsJsonAsync("/", new AddUserCommand()); 49 | 50 | Assert.Equal(422, (int)response.StatusCode); 51 | } 52 | 53 | [Fact] 54 | public async Task Should_return_422_if_existing_user_exists() 55 | { 56 | var appBuilder = new AppBuilder(); 57 | new Startup(new OurTestBStrapper(new NoDbAddUserCommandHandler(userExists: true, newUserId: 1))) 58 | .Configuration(appBuilder); 59 | 60 | var handler = this.GetOwinHttpMessageHandler(appBuilder); 61 | 62 | var client = this.GetHttpClient(handler); 63 | 64 | var response = await client.PostAsJsonAsync("/", new AddUserCommand 65 | { 66 | FirstName = "Vincent", 67 | LastName = "Vega", 68 | Email = "vincent@home.com" 69 | }); 70 | 71 | Assert.Equal(422, (int)response.StatusCode); 72 | } 73 | 74 | private HttpClient GetHttpClient(OwinHttpMessageHandler handler) 75 | { 76 | var client = new HttpClient(handler) 77 | { 78 | BaseAddress = new Uri("http://localhost") 79 | }; 80 | client.DefaultRequestHeaders.Add("Accept", "application/json"); 81 | return client; 82 | } 83 | 84 | private OwinHttpMessageHandler GetOwinHttpMessageHandler(AppBuilder appBuilder) 85 | { 86 | var handler = new OwinHttpMessageHandler(appBuilder.Build()) 87 | { 88 | UseCookies = true 89 | }; 90 | return handler; 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions.Tests/Features/Users/GetUser/GetUserTests.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace MultiDbSupportWithConventions.Tests.Features.Users.GetUser 3 | { 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Net.Http; 7 | using System.Threading.Tasks; 8 | 9 | using FakeItEasy; 10 | 11 | using MediatR; 12 | 13 | using Microsoft.Owin.Builder; 14 | 15 | using MultiDbSupportWithConventions.Features.Users; 16 | using MultiDbSupportWithConventions.Features.Users.GetUser; 17 | 18 | using Xunit; 19 | 20 | public class GetUserTests 21 | { 22 | [Fact] 23 | public async Task Should_return_404_when_user_not_found() 24 | { 25 | var appBuilder = new AppBuilder(); 26 | 27 | var fakeGetUserHandler = A.Fake>(); 28 | A.CallTo(() => fakeGetUserHandler.Handle(A.Ignored)) 29 | .Throws(() => new InvalidOperationException()); 30 | 31 | new Startup(new OurTestBStrapper(getUserRequestHandler: fakeGetUserHandler)).Configuration(appBuilder); 32 | 33 | var handler = new OwinHttpMessageHandler(appBuilder.Build()) 34 | { 35 | UseCookies = true 36 | }; 37 | 38 | var client = new HttpClient(handler) 39 | { 40 | BaseAddress = new Uri("http://localhost") 41 | }; 42 | client.DefaultRequestHeaders.Add("Accept", "application/json"); 43 | 44 | var response = await client.GetAsync("/1"); 45 | 46 | Assert.Equal(404, (int)response.StatusCode); 47 | 48 | } 49 | 50 | [Fact] 51 | public async Task Should_return_200_when_user_found() 52 | { 53 | var appBuilder = new AppBuilder(); 54 | 55 | var fakeGetUserHandler = A.Fake>(); 56 | A.CallTo(() => fakeGetUserHandler.Handle(A.Ignored)) 57 | .Returns(new User()); 58 | 59 | new Startup(new OurTestBStrapper(getUserRequestHandler: fakeGetUserHandler)).Configuration(appBuilder); 60 | 61 | var handler = new OwinHttpMessageHandler(appBuilder.Build()) 62 | { 63 | UseCookies = true 64 | }; 65 | 66 | var client = new HttpClient(handler) 67 | { 68 | BaseAddress = new Uri("http://localhost") 69 | }; 70 | client.DefaultRequestHeaders.Add("Accept", "application/json"); 71 | 72 | var response = await client.GetAsync("/1"); 73 | 74 | Assert.Equal(200, (int)response.StatusCode); 75 | 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions.Tests/Features/Users/GetUsers/GetUsersTests.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupportWithConventions.Tests.Features.Users.GetUsers 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Net.Http; 6 | using System.Threading.Tasks; 7 | 8 | using Microsoft.Owin.Builder; 9 | 10 | using MultiDbSupportWithConventions.Features.Users; 11 | 12 | using Xunit; 13 | 14 | public class GetUsersTests 15 | { 16 | [Fact] 17 | public async Task Should_Return_List_Of_Users() 18 | { 19 | var appBuilder = new AppBuilder(); 20 | new Startup(new OurTestBStrapper()).Configuration(appBuilder); 21 | 22 | var handler = new OwinHttpMessageHandler(appBuilder.Build()) 23 | { 24 | UseCookies = true 25 | }; 26 | 27 | var client = new HttpClient(handler) 28 | { 29 | BaseAddress = new Uri("http://localhost") 30 | }; 31 | client.DefaultRequestHeaders.Add("Accept", "application/json"); 32 | 33 | var response = await client.GetAsync("/"); 34 | var data = await response.Content.ReadAsAsync>(); 35 | 36 | Assert.Equal(1, data.Count); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions.Tests/Features/Users/NoDbAddUserCommandHandler.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupportWithConventions.Tests.Features.Users 2 | { 3 | using MultiDbSupportWithConventions.Features.Users; 4 | using MultiDbSupportWithConventions.Features.Users.AddUser; 5 | 6 | public class NoDbAddUserCommandHandler : AddUserCommandHandler 7 | { 8 | private readonly int newUserId; 9 | private readonly bool userExists; 10 | 11 | public NoDbAddUserCommandHandler(bool userExists, int newUserId) 12 | : base(null) 13 | { 14 | this.userExists = userExists; 15 | this.newUserId = newUserId; 16 | } 17 | 18 | protected override int StoreNewUser(AddUserCommand message) 19 | { 20 | return this.newUserId; 21 | } 22 | 23 | protected override bool UserExists(AddUserCommand message) 24 | { 25 | return this.userExists; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions.Tests/MultiDbSupportWithConventions.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Debug 7 | AnyCPU 8 | {B32B3A52-85DD-49F4-8985-4121E5154A2B} 9 | Library 10 | Properties 11 | MultiDbSupportWithConventions.Tests 12 | MultiDbSupportWithConventions.Tests 13 | v4.5.1 14 | 512 15 | 8c4be68f 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | ..\packages\CsQuery.1.3.3\lib\net40\CsQuery.dll 37 | True 38 | 39 | 40 | ..\packages\FakeItEasy.1.25.3\lib\net40\FakeItEasy.dll 41 | True 42 | 43 | 44 | ..\packages\FluentValidation.5.6.2.0\lib\Net45\FluentValidation.dll 45 | True 46 | 47 | 48 | ..\packages\MediatR.2.0.2\lib\net45\MediatR.dll 49 | True 50 | 51 | 52 | ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll 53 | True 54 | 55 | 56 | ..\packages\Nancy.1.3.0\lib\net40\Nancy.dll 57 | True 58 | 59 | 60 | ..\packages\Nancy.Testing.1.3.0\lib\net40\Nancy.Testing.dll 61 | True 62 | 63 | 64 | ..\packages\Nancy.Validation.FluentValidation.1.3.0\lib\net40\Nancy.Validation.FluentValidation.dll 65 | True 66 | 67 | 68 | ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll 69 | True 70 | 71 | 72 | ..\packages\Owin.1.0\lib\net40\Owin.dll 73 | True 74 | 75 | 76 | ..\packages\OwinHttpMessageHandler.1.3.2\lib\net450\OwinHttpMessageHandler.dll 77 | True 78 | 79 | 80 | 81 | 82 | 83 | ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll 84 | True 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | ..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll 93 | True 94 | 95 | 96 | ..\packages\xunit.assert.2.1.0\lib\portable-net45+win8+wp8+wpa81\xunit.assert.dll 97 | True 98 | 99 | 100 | ..\packages\xunit.extensibility.core.2.1.0\lib\portable-net45+win8+wp8+wpa81\xunit.core.dll 101 | True 102 | 103 | 104 | ..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll 105 | True 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | {645f4720-8dfe-4a4e-bcf1-a6428e6dedd9} 123 | MultiDbSupportWithConventions 124 | 125 | 126 | 127 | 128 | 129 | 130 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 131 | 132 | 133 | 134 | 141 | -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions.Tests/OurTestBStrapper.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupportWithConventions.Tests 2 | { 3 | using System.Collections.Generic; 4 | 5 | using FakeItEasy; 6 | 7 | using MediatR; 8 | 9 | using MultiDbSupportWithConventions.Features.Users; 10 | using MultiDbSupportWithConventions.Features.Users.GetUser; 11 | using MultiDbSupportWithConventions.Features.Users.GetUsers; 12 | using MultiDbSupportWithConventions.Tests.Features.Users; 13 | 14 | using Nancy.TinyIoc; 15 | 16 | public class OurTestBStrapper : Bootstrapper 17 | { 18 | private readonly IRequestHandler getUserRequestHandler; 19 | private readonly IRequestHandler addUserRequestHandler; 20 | 21 | public OurTestBStrapper(IRequestHandler addUserRequestHandler = null, IRequestHandler getUserRequestHandler = null) 22 | { 23 | this.getUserRequestHandler = getUserRequestHandler; 24 | this.addUserRequestHandler = addUserRequestHandler; 25 | } 26 | 27 | protected override void ConfigureApplicationContainer(TinyIoCContainer container) 28 | { 29 | base.ConfigureApplicationContainer(container); 30 | 31 | var fakeUserQuery = A.Fake>>(); 32 | A.CallTo(() => fakeUserQuery.Handle(A.Ignored)) 33 | .Returns(new[] { new User { Email = "fred@home.com" } }); 34 | 35 | container.Register>>(fakeUserQuery); 36 | container.Register>(this.addUserRequestHandler); 37 | container.Register>(this.getUserRequestHandler); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | using Xunit; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | 10 | [assembly: AssemblyTitle("MultiDbSupportWithConventions.Tests")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("MultiDbSupportWithConventions.Tests")] 15 | [assembly: AssemblyCopyright("Copyright © 2015")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | 23 | [assembly: ComVisible(false)] 24 | 25 | // The following GUID is for the ID of the typelib if this project is exposed to COM 26 | 27 | [assembly: Guid("1a56eaee-b71f-4fcc-9e5b-c8435fe5368b")] 28 | 29 | // Version information for an assembly consists of the following four values: 30 | // 31 | // Major Version 32 | // Minor Version 33 | // Build Number 34 | // Revision 35 | // 36 | // You can specify all the values or you can default the Build and Revision Numbers 37 | // by using the '*' as shown below: 38 | // [assembly: AssemblyVersion("1.0.*")] 39 | 40 | [assembly: AssemblyVersion("1.0.0.0")] 41 | [assembly: AssemblyFileVersion("1.0.0.0")] 42 | 43 | 44 | [assembly: CollectionBehavior(DisableTestParallelization = true)] //TODO Remove when FakeItEasy releases version > 1.25.3 -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultiDbSupportWithConventions", "MultiDbSupportWithConventions\MultiDbSupportWithConventions.csproj", "{645F4720-8DFE-4A4E-BCF1-A6428E6DEDD9}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultiDbSupportWithConventions.Tests", "MultiDbSupportWithConventions.Tests\MultiDbSupportWithConventions.Tests.csproj", "{B32B3A52-85DD-49F4-8985-4121E5154A2B}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {645F4720-8DFE-4A4E-BCF1-A6428E6DEDD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {645F4720-8DFE-4A4E-BCF1-A6428E6DEDD9}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {645F4720-8DFE-4A4E-BCF1-A6428E6DEDD9}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {645F4720-8DFE-4A4E-BCF1-A6428E6DEDD9}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {B32B3A52-85DD-49F4-8985-4121E5154A2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {B32B3A52-85DD-49F4-8985-4121E5154A2B}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {B32B3A52-85DD-49F4-8985-4121E5154A2B}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {B32B3A52-85DD-49F4-8985-4121E5154A2B}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Bootstrapper.cs: -------------------------------------------------------------------------------- 1 | using MultiDbSupportWithConventions.Features.Users.DataAccess; 2 | 3 | namespace MultiDbSupportWithConventions 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Configuration; 8 | 9 | using MediatR; 10 | 11 | using MultiDbSupportWithConventions.Features.Users; 12 | using MultiDbSupportWithConventions.Features.Users.AddUser; 13 | using MultiDbSupportWithConventions.Features.Users.GetUser; 14 | using MultiDbSupportWithConventions.Features.Users.GetUsers; 15 | using MultiDbSupportWithConventions.Features.Users.UpdateUser; 16 | 17 | using Nancy; 18 | using Nancy.TinyIoc; 19 | 20 | public class Bootstrapper : DefaultNancyBootstrapper 21 | { 22 | protected override void ApplicationStartup(TinyIoCContainer container, Nancy.Bootstrapper.IPipelines pipelines) 23 | { 24 | base.ApplicationStartup(container, pipelines); 25 | 26 | StaticConfiguration.DisableErrorTraces = false; 27 | } 28 | 29 | protected override void ConfigureApplicationContainer(TinyIoCContainer container) 30 | { 31 | //base.ConfigureApplicationContainer(container); // Lets our app do all the wiring up 32 | 33 | //Instead of having stored procedures do the abstraction we can tell IOC to do it. 34 | //That way we have commands/queries that can use db specific sql for example. 35 | switch (ConfigurationManager.ConnectionStrings["mydb"].ProviderName.ToLower()) 36 | { 37 | case "system.data.sqlclient": 38 | container.Register(); 39 | container.Register>, MssqlUserListQueryRequestHandler>(); 40 | container.Register, MsSqlAddUserCommandHandlerCommandHandlerHandler>(); 41 | container.Register, AgnosticDbGetUserQueryHandler>(); 42 | container.Register, UpdateUserCommandHandler>(); 43 | container.Register, DeleteUserCommandHandler>(); 44 | container.Register(); 45 | break; 46 | case "npgsql": 47 | container.Register(); 48 | container.Register>, NpgsqlUserListQueryRequestHandler>(); 49 | container.Register, AgnosticDbGetUserQueryHandler>(); 50 | container.Register, UpdateUserCommandHandler>(); 51 | container.Register, DeleteUserCommandHandler>(); 52 | container.Register(); 53 | container.Register>, NpgsqlUserListQueryPagingHandler>(); 54 | break; 55 | default: 56 | throw new ArgumentException("Invalid ProviderName in connection string."); 57 | } 58 | container.Register(new Mediator(container.Resolve, container.ResolveAll)); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Features/Users/AddUser/AddUserCommand.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupportWithConventions.Features.Users 2 | { 3 | using MediatR; 4 | 5 | public class AddUserCommand : IRequest 6 | { 7 | public string FirstName { get; set; } 8 | public string LastName { get; set; } 9 | public string Email { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Features/Users/AddUser/AddUserCommandHandler.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupportWithConventions.Features.Users.AddUser 2 | { 3 | using System; 4 | 5 | using FluentValidation; 6 | using FluentValidation.Results; 7 | 8 | using MediatR; 9 | 10 | public abstract class AddUserCommandHandler : IRequestHandler 11 | { 12 | protected readonly IDbConnectionProvider connectionProvider; 13 | 14 | public AddUserCommandHandler(IDbConnectionProvider connectionProvider) 15 | { 16 | this.connectionProvider = connectionProvider; 17 | } 18 | 19 | public int Handle(AddUserCommand message) 20 | { 21 | //This method can have unit tests or acceptance tests as shown in the MultiDbSupportWithConventions.Tests project 22 | 23 | //Move validation out of modules and validate here and throw on error for module to catch 24 | var validator = new AddUserValidator(); 25 | validator.ValidateAndThrow(message); 26 | 27 | //Contrived shared logic across shared across multi db implementations 28 | var userAlreadyExist = this.UserExists(message); 29 | 30 | if (userAlreadyExist) 31 | { 32 | throw new ValidationException(new[]{new ValidationFailure("Email","User with this email already exists") }); 33 | } 34 | 35 | var id = this.StoreNewUser(message); 36 | 37 | return id; 38 | } 39 | 40 | protected abstract int StoreNewUser(AddUserCommand message); 41 | protected abstract bool UserExists(AddUserCommand message); 42 | } 43 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Features/Users/AddUser/AddUserValidator.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupportWithConventions.Features.Users.AddUser 2 | { 3 | using FluentValidation; 4 | 5 | public class AddUserValidator : AbstractValidator 6 | { 7 | public AddUserValidator() 8 | { 9 | this.RuleFor(x => x.Email).NotEmpty(); 10 | this.RuleFor(x => x.FirstName).NotEmpty(); 11 | this.RuleFor(x => x.LastName).NotEmpty(); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Features/Users/AddUser/MsSqlAddUserCommandHandler.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupportWithConventions.Features.Users.AddUser 2 | { 3 | using System.Linq; 4 | 5 | using Dapper; 6 | 7 | public class MsSqlAddUserCommandHandlerCommandHandlerHandler : AddUserCommandHandler 8 | { 9 | public MsSqlAddUserCommandHandlerCommandHandlerHandler(IDbConnectionProvider connectionProvider) 10 | : base(connectionProvider) 11 | { 12 | } 13 | 14 | protected override int StoreNewUser(AddUserCommand message) 15 | { 16 | //This will need some integration tests against a db 17 | using (var conn = this.connectionProvider.GetConnection()) 18 | { 19 | return conn.Query(@" 20 | insert into users(firstname,lastname,email) values (@firstname,@lastname,@email); 21 | select cast(SCOPE_IDENTITY() as int)", 22 | new {firstname = message.FirstName, lastname = message.LastName, email = message.Email}) 23 | .Single(); 24 | } 25 | } 26 | 27 | protected override bool UserExists(AddUserCommand message) 28 | { 29 | //This will some integration tests against a db 30 | using (var conn = this.connectionProvider.GetConnection()) 31 | { 32 | var count = conn.ExecuteScalar("select count(*) from users where email = @email", 33 | new {email = message.Email}); 34 | return count > 0; 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Features/Users/DataAccess/AbstractNpgsqlPagingRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MediatR; 3 | 4 | namespace MultiDbSupportWithConventions 5 | { 6 | public abstract class AbstractNpgsqlPagingRequestHandler : IRequestHandler where T : IRequest 7 | { 8 | public abstract U Handle(T message); 9 | 10 | public string SQL { get; set; } 11 | 12 | public string PagingSQL 13 | { 14 | get 15 | { 16 | return string.Concat("select count(items.id) over() as totalitemcount, items.* from (", this.SQL, ") as items limit @Limit offset @Offset"); 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Features/Users/DataAccess/GetUserByIdQuery.cs: -------------------------------------------------------------------------------- 1 | using MultiDbSupportWithConventions.Features.Users; 2 | using Dapper; 3 | using System.Linq; 4 | using System; 5 | 6 | namespace MultiDbSupportWithConventions.Features.Users.DataAccess 7 | { 8 | public class GetUserByIdQuery : IGetUserByIdQuery 9 | { 10 | private readonly IDbConnectionProvider dbConnectionProvider; 11 | 12 | public GetUserByIdQuery (IDbConnectionProvider dbConnectionProvider) 13 | { 14 | this.dbConnectionProvider = dbConnectionProvider; 15 | 16 | } 17 | public User Execute(int id) 18 | { 19 | using (var conn = this.dbConnectionProvider.GetConnection()) 20 | { 21 | var user = 22 | conn.Query("select * from users where id = @id", new {id = id}).FirstOrDefault(); 23 | 24 | if (user == null) 25 | { 26 | throw new InvalidOperationException("Unable to find user with id : " + id); 27 | } 28 | 29 | return user; 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Features/Users/DataAccess/IGetUserByIdQuery.cs: -------------------------------------------------------------------------------- 1 | using MultiDbSupportWithConventions.Features.Users; 2 | 3 | namespace MultiDbSupportWithConventions.Features.Users.DataAccess 4 | { 5 | public interface IGetUserByIdQuery 6 | { 7 | User Execute(int id); 8 | } 9 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Features/Users/DeleteUser/DeleteUserCommand.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace MultiDbSupportWithConventions 4 | { 5 | public class DeleteUserCommand : IRequest 6 | { 7 | public DeleteUserCommand(int id) 8 | { 9 | this.Id = id; 10 | } 11 | 12 | public int Id { get; private set; } 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Features/Users/DeleteUser/DeleteUserCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using MultiDbSupportWithConventions.Features.Users.DataAccess; 3 | using Dapper; 4 | 5 | namespace MultiDbSupportWithConventions 6 | { 7 | public class DeleteUserCommandHandler : IRequestHandler 8 | { 9 | private readonly IDbConnectionProvider dbConnectionProvider; 10 | private readonly IGetUserByIdQuery getUserByIdQuery; 11 | 12 | public DeleteUserCommandHandler(IDbConnectionProvider dbConnectionProvider, IGetUserByIdQuery getUserByIdQuery) 13 | { 14 | //A shared query that can be used by other handlers by injecting it. This is a class with one method not a repository with lots of methods 15 | this.getUserByIdQuery = getUserByIdQuery; 16 | 17 | this.dbConnectionProvider = dbConnectionProvider; 18 | } 19 | 20 | public int Handle(DeleteUserCommand message) 21 | { 22 | //You wouldn't do this normally as you can just try and delete without getting the user first but 23 | //just an example of using a shared query class 24 | var user = this.getUserByIdQuery.Execute(message.Id); 25 | 26 | using (var conn = this.dbConnectionProvider.GetConnection()) 27 | { 28 | return conn.Execute("delete from user where id = @id", 29 | new {id = message.Id}); 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Features/Users/GetUser/AgnosticDbGetUserQueryHandler.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupportWithConventions.Features.Users.GetUser 2 | { 3 | using System; 4 | using System.Linq; 5 | 6 | using Dapper; 7 | 8 | using MediatR; 9 | 10 | public class AgnosticDbGetUserQueryHandler : IRequestHandler 11 | { 12 | private readonly IDbConnectionProvider dbConnectionProvider; 13 | 14 | public AgnosticDbGetUserQueryHandler(IDbConnectionProvider dbConnectionProvider) 15 | { 16 | this.dbConnectionProvider = dbConnectionProvider; 17 | } 18 | 19 | public User Handle(GetUserQuery message) 20 | { 21 | //This class will need to be tested against a live database in an integration test 22 | using (var conn = this.dbConnectionProvider.GetConnection()) 23 | { 24 | var user = 25 | conn.Query("select * from users where id = @id", new {id = message.UserId}).FirstOrDefault(); 26 | 27 | if (user == null) 28 | { 29 | throw new InvalidOperationException("Unable to find user with id : " + message.UserId); 30 | } 31 | 32 | return user; 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Features/Users/GetUser/GetUserQuery.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupportWithConventions.Features.Users.GetUser 2 | { 3 | using MediatR; 4 | 5 | public class GetUserQuery : IRequest 6 | { 7 | public GetUserQuery(int userId) 8 | { 9 | this.UserId = userId; 10 | } 11 | 12 | public int UserId { get; private set; } 13 | } 14 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Features/Users/GetUsers/MsSqlUserListQueryRequestHandler.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupportWithConventions.Features.Users.GetUsers 2 | { 3 | using System.Collections.Generic; 4 | 5 | using Dapper; 6 | 7 | using MediatR; 8 | 9 | public class MssqlUserListQueryRequestHandler : IRequestHandler> 10 | { 11 | private readonly IDbConnectionProvider dbConnectionProvider; 12 | 13 | public MssqlUserListQueryRequestHandler(IDbConnectionProvider dbConnectionProvider) 14 | { 15 | this.dbConnectionProvider = dbConnectionProvider; 16 | } 17 | 18 | public IEnumerable Handle(UserListQuery message) 19 | { 20 | //This should have an intgreation test 21 | using (var dbConnection = this.dbConnectionProvider.GetConnection()) 22 | { 23 | //Here we can do MSSQL specific sql if needs be 24 | var data = dbConnection.Query("select * from users"); 25 | return data; 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Features/Users/GetUsers/NpgsqlUserListQueryRequestHandler.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupportWithConventions.Features.Users.GetUsers 2 | { 3 | using System.Collections.Generic; 4 | 5 | using Dapper; 6 | 7 | using MediatR; 8 | 9 | public class NpgsqlUserListQueryRequestHandler : IRequestHandler> 10 | { 11 | private readonly IDbConnectionProvider dbConnectionProvider; 12 | 13 | public NpgsqlUserListQueryRequestHandler(IDbConnectionProvider dbConnectionProvider) 14 | { 15 | this.dbConnectionProvider = dbConnectionProvider; 16 | } 17 | 18 | public IEnumerable Handle(UserListQuery message) 19 | { 20 | //This should have an intgreation test 21 | using (var dbConnection = this.dbConnectionProvider.GetConnection()) 22 | { 23 | //Here we can do postgres specific sql if needs be 24 | var data = dbConnection.Query("select * from users;"); 25 | return data; 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Features/Users/GetUsers/UserListQuery.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupportWithConventions.Features.Users.GetUsers 2 | { 3 | using System.Collections.Generic; 4 | 5 | using MediatR; 6 | 7 | public class UserListQuery : IRequest> 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Features/Users/GetUsersPaged/NpgsqlUserListQueryPagingHandler.cs: -------------------------------------------------------------------------------- 1 | using MultiDbSupportWithConventions.Features.Users.GetUsers; 2 | using System.Collections.Generic; 3 | using MultiDbSupportWithConventions.Features.Users; 4 | using Dapper; 5 | 6 | namespace MultiDbSupportWithConventions 7 | { 8 | public class NpgsqlUserListQueryPagingHandler : AbstractNpgsqlPagingRequestHandler> 9 | { 10 | private IDbConnectionProvider dbConnectionProvider; 11 | 12 | public NpgsqlUserListQueryPagingHandler(IDbConnectionProvider dbConnectionProvider) 13 | { 14 | this.dbConnectionProvider = dbConnectionProvider; 15 | this.SQL = "select * from users"; 16 | } 17 | 18 | public override IEnumerable Handle(PagedUserListQuery message) 19 | { 20 | using (var dbConnection = this.dbConnectionProvider.GetConnection()) 21 | { 22 | //Here we can do postgres paging specific sql if needs be 23 | var data = dbConnection.Query(this.PagingSQL, 24 | new 25 | { 26 | Limit = 20, 27 | Offset = 0 28 | }); 29 | 30 | return data; 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Features/Users/GetUsersPaged/PagedUserListQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MediatR; 3 | using System.Collections.Generic; 4 | using MultiDbSupportWithConventions.Features.Users; 5 | 6 | namespace MultiDbSupportWithConventions 7 | { 8 | public class PagedUserListQuery: IRequest> 9 | { 10 | 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Features/Users/UpdateUser/UpdateUserCommand.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace MultiDbSupportWithConventions.Features.Users.UpdateUser 4 | { 5 | public class UpdateUserCommand : IRequest 6 | { 7 | public int Id { get; set; } 8 | public string FirstName { get; set; } 9 | public string LastName {get;set;} 10 | public string Email { get; set;} 11 | } 12 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Features/Users/UpdateUser/UpdateUserCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using MultiDbSupportWithConventions.Features.Users.GetUser; 3 | using Dapper; 4 | 5 | namespace MultiDbSupportWithConventions.Features.Users.UpdateUser 6 | { 7 | public class UpdateUserCommandHandler : IRequestHandler 8 | { 9 | private readonly IRequestHandler getUserQueryHandler; 10 | private readonly IDbConnectionProvider dbConnectionProvider; 11 | 12 | public UpdateUserCommandHandler(IDbConnectionProvider dbConnectionProvider, IRequestHandler getUserQueryHandler) 13 | { 14 | //Inject another query handler to re-use some code. 15 | //NOTE: I DO NOT LIKE THIS, see DeleteUserCommandHandler for better approach 16 | this.getUserQueryHandler = getUserQueryHandler; 17 | 18 | this.dbConnectionProvider = dbConnectionProvider; 19 | } 20 | 21 | public int Handle(UpdateUserCommand message) 22 | { 23 | //You wouldn't do this normally as you can just try and update without getting the user first but 24 | //just an example of using a shared queryhandler class 25 | var existingUser = this.getUserQueryHandler.Handle(new GetUserQuery(message.Id)); 26 | 27 | using (var conn = this.dbConnectionProvider.GetConnection()) 28 | { 29 | return conn.Execute(@" 30 | uodate users set firstname = @firstnmae, lastname = @lastname,email = @email where id = @id", 31 | new {firstname = message.FirstName, lastname = message.LastName, email = message.Email, id = message.Id}); 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Features/Users/User.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupportWithConventions.Features.Users 2 | { 3 | public class User 4 | { 5 | public int Id { get; set; } 6 | public string FirstName { get; set; } 7 | public string LastName { get; set; } 8 | public string Email { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Features/Users/UserModule.cs: -------------------------------------------------------------------------------- 1 | using MultiDbSupportWithConventions.Features.Users.UpdateUser; 2 | using Nancy.Routing; 3 | 4 | namespace MultiDbSupportWithConventions.Features.Users 5 | { 6 | using System; 7 | using System.Linq; 8 | 9 | using FluentValidation; 10 | 11 | using MediatR; 12 | 13 | using MultiDbSupportWithConventions.Features.Users.GetUser; 14 | using MultiDbSupportWithConventions.Features.Users.GetUsers; 15 | 16 | using Nancy; 17 | using Nancy.ModelBinding; 18 | 19 | public class UserModule : NancyModule 20 | { 21 | public UserModule(IMediator mediator) 22 | { 23 | this.Get["/"] = _ => 24 | { 25 | var query = new UserListQuery(); 26 | 27 | return mediator.Send(query); 28 | }; 29 | 30 | this.Get["/paging"] = _ => 31 | { 32 | var query = new UserListQuery(); 33 | return mediator.Send(query); 34 | }; 35 | 36 | this.Get["/{id:int}"] = parameters => 37 | { 38 | var query = new GetUserQuery((int)parameters.id); 39 | try 40 | { 41 | return mediator.Send(query); 42 | } 43 | catch (InvalidOperationException) 44 | { 45 | return 404; 46 | } 47 | }; 48 | 49 | this.Post["/"] = _ => 50 | { 51 | var incomingModel = this.Bind(); 52 | 53 | try 54 | { 55 | var id = mediator.Send(incomingModel); 56 | 57 | return this.Negotiate.WithStatusCode(201).WithHeader("Location", "http://example.com/" + id); 58 | } 59 | catch (ValidationException ex) 60 | { 61 | return 62 | Negotiate.WithModel(ex.Errors.Select(x => new {x.PropertyName, x.ErrorMessage})) 63 | .WithStatusCode(HttpStatusCode.UnprocessableEntity); 64 | } 65 | }; 66 | 67 | this.Put["/{id:int}"] = _ => 68 | { 69 | var updateCmd = this.Bind(); 70 | try 71 | { 72 | var result = mediator.Send(updateCmd); 73 | 74 | return 204; 75 | } 76 | catch (InvalidOperationException ex) 77 | { 78 | return 404; 79 | } 80 | }; 81 | 82 | this.Delete["/{id:int}"] = parameters => 83 | { 84 | int id = parameters.id; 85 | var deleteUserCommand = new DeleteUserCommand(id); 86 | try 87 | { 88 | var result = mediator.Send(deleteUserCommand); 89 | return 204; 90 | } 91 | catch (InvalidOperationException) 92 | { 93 | return 404; 94 | } 95 | }; 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/IDbConnectionProvider.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupportWithConventions 2 | { 3 | using System.Data; 4 | 5 | public interface IDbConnectionProvider 6 | { 7 | IDbConnection GetConnection(); 8 | } 9 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/MultiDbSupportWithConventions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {645F4720-8DFE-4A4E-BCF1-A6428E6DEDD9} 8 | Exe 9 | Properties 10 | MultiDbSupportWithConventions 11 | MultiDbSupportWithConventions 12 | v4.5.1 13 | 512 14 | true 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | true 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\Dapper.1.42\lib\net45\Dapper.dll 38 | 39 | 40 | ..\packages\MediatR.2.0.2\lib\net45\MediatR.dll 41 | 42 | 43 | ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll 44 | 45 | 46 | ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll 47 | 48 | 49 | ..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll 50 | 51 | 52 | ..\packages\Nancy.1.3.0\lib\net40\Nancy.dll 53 | 54 | 55 | ..\packages\Nancy.Owin.1.3.0\lib\net40\Nancy.Owin.dll 56 | 57 | 58 | ..\packages\Npgsql.3.0.4\lib\net45\Npgsql.dll 59 | 60 | 61 | ..\packages\Owin.1.0\lib\net40\Owin.dll 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | ..\packages\FluentValidation.5.6.2.0\lib\Net45\FluentValidation.dll 70 | 71 | 72 | ..\packages\Nancy.Validation.FluentValidation.1.3.0\lib\net40\Nancy.Validation.FluentValidation.dll 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 122 | -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/PostgresConnectionProvider.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupportWithConventions 2 | { 3 | using System.Configuration; 4 | using System.Data; 5 | 6 | using Npgsql; 7 | 8 | public class PostgresConnectionProvider : IDbConnectionProvider 9 | { 10 | private readonly string connectionString; 11 | 12 | public PostgresConnectionProvider() 13 | { 14 | this.connectionString = ConfigurationManager.ConnectionStrings["mydb"].ConnectionString; 15 | } 16 | 17 | public IDbConnection GetConnection() 18 | { 19 | var connection = new NpgsqlConnection(this.connectionString); 20 | connection.Open(); 21 | return connection; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Program.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupportWithConventions 2 | { 3 | using System; 4 | 5 | using Microsoft.Owin.Hosting; 6 | 7 | internal class Program 8 | { 9 | private static void Main(string[] args) 10 | { 11 | using (WebApp.Start("http://+:5678")) 12 | { 13 | Console.WriteLine("Running on http://localhost:5678"); 14 | Console.WriteLine("Press enter to exit"); 15 | Console.ReadLine(); 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | 8 | [assembly: AssemblyTitle("MultiDbSupportWithConventions")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("MultiDbSupportWithConventions")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | 21 | [assembly: ComVisible(false)] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | 25 | [assembly: Guid("93d8f40b-160e-4720-ade8-04bb8ce9f566")] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [assembly: AssemblyVersion("1.0.*")] 37 | 38 | [assembly: AssemblyVersion("1.0.0.0")] 39 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/SqlServerConnectionProvider.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupportWithConventions 2 | { 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Data.SqlClient; 6 | 7 | /// 8 | /// Implement the IDbConnectionProvider interface. 9 | /// 10 | public class SqlServerConnectionProvider : IDbConnectionProvider 11 | { 12 | private readonly string connectionString; 13 | 14 | public SqlServerConnectionProvider() 15 | { 16 | this.connectionString = ConfigurationManager.ConnectionStrings["MyDb"].ConnectionString; 17 | } 18 | 19 | /// 20 | /// Return an open SqlConnection to the VQ database. 21 | /// 22 | /// 23 | public IDbConnection GetConnection() 24 | { 25 | var connection = new SqlConnection(this.connectionString); 26 | connection.Open(); 27 | return connection; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace MultiDbSupportWithConventions 2 | { 3 | using Nancy.Bootstrapper; 4 | 5 | using Owin; 6 | 7 | public class Startup 8 | { 9 | private readonly INancyBootstrapper bootstrapper; 10 | 11 | public Startup(INancyBootstrapper bootstrapper) 12 | { 13 | this.bootstrapper = bootstrapper; 14 | } 15 | 16 | public Startup() 17 | { 18 | this.bootstrapper = new Bootstrapper(); 19 | } 20 | 21 | public void Configuration(IAppBuilder app) 22 | { 23 | app.UseNancy(options => options.Bootstrapper = this.bootstrapper); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/MultiDbSupportWithConventions/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/MultiDbSupportWithConventions/db.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchannon/LayerAllTheThings/2c34faf32d8892d5b3fc08f2c1abdd49357db56f/src/MultiDbSupportWithConventions/db.sql -------------------------------------------------------------------------------- /src/TraditionalLayering/TraditionalLayering.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.40629.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TraditionalLayering", "TraditionalLayering\TraditionalLayering.csproj", "{A5F2C32C-753E-4BB0-BB28-AF028A8C2DD0}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TraditionalLayering_Tests", "TraditionalLayering_Tests\TraditionalLayering_Tests.csproj", "{871FEDA6-B351-4965-92F3-4657E32A34D6}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|Mixed Platforms = Debug|Mixed Platforms 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|Mixed Platforms = Release|Mixed Platforms 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {A5F2C32C-753E-4BB0-BB28-AF028A8C2DD0}.Debug|Any CPU.ActiveCfg = Debug|x86 21 | {A5F2C32C-753E-4BB0-BB28-AF028A8C2DD0}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 22 | {A5F2C32C-753E-4BB0-BB28-AF028A8C2DD0}.Debug|Mixed Platforms.Build.0 = Debug|x86 23 | {A5F2C32C-753E-4BB0-BB28-AF028A8C2DD0}.Debug|x86.ActiveCfg = Debug|x86 24 | {A5F2C32C-753E-4BB0-BB28-AF028A8C2DD0}.Debug|x86.Build.0 = Debug|x86 25 | {A5F2C32C-753E-4BB0-BB28-AF028A8C2DD0}.Release|Any CPU.ActiveCfg = Release|x86 26 | {A5F2C32C-753E-4BB0-BB28-AF028A8C2DD0}.Release|Mixed Platforms.ActiveCfg = Release|x86 27 | {A5F2C32C-753E-4BB0-BB28-AF028A8C2DD0}.Release|Mixed Platforms.Build.0 = Release|x86 28 | {A5F2C32C-753E-4BB0-BB28-AF028A8C2DD0}.Release|x86.ActiveCfg = Release|x86 29 | {A5F2C32C-753E-4BB0-BB28-AF028A8C2DD0}.Release|x86.Build.0 = Release|x86 30 | {871FEDA6-B351-4965-92F3-4657E32A34D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {871FEDA6-B351-4965-92F3-4657E32A34D6}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {871FEDA6-B351-4965-92F3-4657E32A34D6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 33 | {871FEDA6-B351-4965-92F3-4657E32A34D6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 34 | {871FEDA6-B351-4965-92F3-4657E32A34D6}.Debug|x86.ActiveCfg = Debug|Any CPU 35 | {871FEDA6-B351-4965-92F3-4657E32A34D6}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {871FEDA6-B351-4965-92F3-4657E32A34D6}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {871FEDA6-B351-4965-92F3-4657E32A34D6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 38 | {871FEDA6-B351-4965-92F3-4657E32A34D6}.Release|Mixed Platforms.Build.0 = Release|Any CPU 39 | {871FEDA6-B351-4965-92F3-4657E32A34D6}.Release|x86.ActiveCfg = Release|Any CPU 40 | EndGlobalSection 41 | GlobalSection(SolutionProperties) = preSolution 42 | HideSolutionNode = FALSE 43 | EndGlobalSection 44 | EndGlobal 45 | -------------------------------------------------------------------------------- /src/TraditionalLayering/TraditionalLayering/Model/Person.cs: -------------------------------------------------------------------------------- 1 | namespace TraditionalLayering.Model { 2 | public class Person 3 | { 4 | public int Id { get; set; } 5 | 6 | public string FirstName{ get; set; } 7 | 8 | public string LastName{ get; set; } 9 | 10 | public string FullName{ get { return FirstName + " " + LastName; } } 11 | 12 | public string EmailAddress { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /src/TraditionalLayering/TraditionalLayering/NancyModule/PersonModule.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Nancy; 3 | using Nancy.ModelBinding; 4 | using TraditionalLayering.Model; 5 | using TraditionalLayering.Service; 6 | 7 | namespace TraditionalLayering.NancyModule { 8 | public class PersonModule : Nancy.NancyModule 9 | { 10 | 11 | public PersonModule(IAccountService accountService) 12 | { 13 | Get["/"] = _ => "Hi Earth People!"; 14 | 15 | Get["/{id:int}"] = parameters => 16 | { 17 | var person = accountService.GetLoggedInUser((int)parameters.id); 18 | return person; 19 | }; 20 | 21 | Post["/"] = parameters => 22 | { 23 | var person = this.Bind(); 24 | 25 | var errors = accountService.Create(person); 26 | 27 | if (errors.Any()) 28 | { 29 | foreach (var item in errors) 30 | { 31 | ModelValidationResult.Errors.Add("Person", item); 32 | } 33 | return ModelValidationResult.Errors; 34 | } 35 | 36 | return 201; 37 | }; 38 | 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/TraditionalLayering/TraditionalLayering/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using Microsoft.Owin.Hosting; 4 | 5 | namespace TraditionalLayering 6 | { 7 | class MainClass 8 | { 9 | public static void Main(string[] args) 10 | { 11 | using (WebApp.Start("http://+:5678")) 12 | { 13 | Console.WriteLine("Running on http://localhost:5678"); 14 | Console.WriteLine("Press enter to exit"); 15 | Console.ReadLine(); 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/TraditionalLayering/TraditionalLayering/Repository/AccountRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using TraditionalLayering.Model; 4 | 5 | namespace TraditionalLayering.Repository { 6 | public class AccountRepository : IAccountRepository 7 | { 8 | //Our DB 9 | public static List data = new List() 10 | { 11 | { new Person{ Id = 1, FirstName = "Jim", LastName = "Parsons", EmailAddress = "jim@parsons.com" } }, 12 | { new Person{ Id = 2, FirstName = "Fred", LastName = "Smith", EmailAddress = "fred@smith.com" } }, 13 | { new Person{ Id = 3,FirstName = "Bob", LastName = "Hope", EmailAddress = "bob@hope.com" } }, 14 | { new Person{ Id = 4, FirstName = "Bernard", LastName = "Targarian", EmailAddress = "bernard@targarian.com" } }, 15 | { new Person{ Id = 5, FirstName = "Troy", LastName = "Vega", EmailAddress = "troy@vega.com" } } 16 | }; 17 | 18 | 19 | public Person GetLoggedInUser(int id) 20 | { 21 | return data.FirstOrDefault(x => x.Id == id); 22 | } 23 | 24 | public Person GetUserByEmail(string emailAddress) 25 | { 26 | return data.FirstOrDefault(x => x.EmailAddress == emailAddress); 27 | } 28 | 29 | public void Create(Person person) { 30 | var maxId = data.Max(x => x.Id); 31 | person.Id = maxId + 1; 32 | data.Add(person); 33 | } 34 | 35 | } 36 | } -------------------------------------------------------------------------------- /src/TraditionalLayering/TraditionalLayering/Repository/IAccountRepository.cs: -------------------------------------------------------------------------------- 1 | using TraditionalLayering.Model; 2 | 3 | namespace TraditionalLayering.Repository { 4 | public interface IAccountRepository 5 | { 6 | Person GetLoggedInUser(int id); 7 | 8 | Person GetUserByEmail(string emailAddress); 9 | 10 | void Create(Person person); 11 | } 12 | } -------------------------------------------------------------------------------- /src/TraditionalLayering/TraditionalLayering/Service/AccountService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using TraditionalLayering.Model; 3 | using TraditionalLayering.Repository; 4 | 5 | namespace TraditionalLayering.Service { 6 | public class AccountService:IAccountService 7 | { 8 | private readonly IAccountRepository accountRepository; 9 | 10 | public AccountService(IAccountRepository accountRepository) 11 | { 12 | this.accountRepository = accountRepository; 13 | } 14 | 15 | public Person GetLoggedInUser(int id) 16 | { 17 | //Hit another layer just because the service isnt responsible for retrieving data 18 | return this.accountRepository.GetLoggedInUser(id); 19 | } 20 | 21 | public List Create(Person person) 22 | { 23 | var errorList = new List(); 24 | var existingPerson = this.accountRepository.GetUserByEmail(person.EmailAddress); 25 | 26 | if (existingPerson != null) 27 | { 28 | errorList.Add("User already exists"); 29 | return errorList; 30 | } 31 | 32 | //Other business logic that might do checks and return errors 33 | 34 | this.accountRepository.Create(person); 35 | 36 | return errorList; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/TraditionalLayering/TraditionalLayering/Service/IAccountService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using TraditionalLayering.Model; 3 | 4 | namespace TraditionalLayering.Service { 5 | public interface IAccountService 6 | { 7 | Person GetLoggedInUser(int id); 8 | 9 | List Create(Person person); 10 | } 11 | } -------------------------------------------------------------------------------- /src/TraditionalLayering/TraditionalLayering/Startup.cs: -------------------------------------------------------------------------------- 1 | using Owin; 2 | 3 | namespace TraditionalLayering { 4 | public class Startup 5 | { 6 | public void Configuration(IAppBuilder app) 7 | { 8 | app.UseNancy(); 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/TraditionalLayering/TraditionalLayering/TraditionalLayering.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | {A5F2C32C-753E-4BB0-BB28-AF028A8C2DD0} 7 | Exe 8 | TraditionalLayering 9 | TraditionalLayering 10 | v4.5 11 | 12 | 13 | true 14 | full 15 | false 16 | bin\Debug 17 | DEBUG; 18 | prompt 19 | 4 20 | true 21 | AnyCPU 22 | 23 | 24 | full 25 | true 26 | bin\Release 27 | prompt 28 | 4 29 | true 30 | x86 31 | 32 | 33 | 34 | 35 | ..\packages\Owin.1.0\lib\net40\Owin.dll 36 | 37 | 38 | ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll 39 | 40 | 41 | ..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll 42 | 43 | 44 | ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll 45 | 46 | 47 | 48 | 49 | ..\packages\Nancy.1.2.0\lib\net40\Nancy.dll 50 | 51 | 52 | ..\packages\Nancy.Owin.1.2.0\lib\net40\Nancy.Owin.dll 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/TraditionalLayering/TraditionalLayering/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/TraditionalLayering/TraditionalLayering/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/TraditionalLayering/TraditionalLayering_Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TraditionalLayering_Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TraditionalLayering_Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("7b69cb13-73e7-49c1-96df-5e1d7daab350")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/TraditionalLayering/TraditionalLayering_Tests/TraditionalLayering_Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Debug 7 | AnyCPU 8 | {871FEDA6-B351-4965-92F3-4657E32A34D6} 9 | Library 10 | Properties 11 | TraditionalLayering_Tests 12 | TraditionalLayering_Tests 13 | v4.5 14 | 512 15 | 872796e3 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | AnyCPU 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\NSubstitute.1.9.1.0\lib\net45\NSubstitute.dll 38 | True 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | ..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll 49 | True 50 | 51 | 52 | ..\packages\xunit.assert.2.1.0\lib\portable-net45+win8+wp8+wpa81\xunit.assert.dll 53 | True 54 | 55 | 56 | ..\packages\xunit.extensibility.core.2.1.0\lib\portable-net45+win8+wp8+wpa81\xunit.core.dll 57 | True 58 | 59 | 60 | ..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll 61 | True 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | {a5f2c32c-753e-4bb0-bb28-af028a8c2dd0} 75 | TraditionalLayering 76 | 77 | 78 | 79 | 80 | 81 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 82 | 83 | 84 | 85 | 92 | -------------------------------------------------------------------------------- /src/TraditionalLayering/TraditionalLayering_Tests/When_creating_a_user.cs: -------------------------------------------------------------------------------- 1 | using NSubstitute; 2 | using TraditionalLayering; 3 | using TraditionalLayering.Model; 4 | using TraditionalLayering.Repository; 5 | using TraditionalLayering.Service; 6 | using Xunit; 7 | 8 | namespace TraditionalLayering_Tests 9 | { 10 | 11 | public class When_creating_a_user 12 | { 13 | [Fact] 14 | public void Should_return_error() 15 | { 16 | var repository = Substitute.For(); 17 | repository.GetUserByEmail(Arg.Any()).Returns(new Person()); 18 | 19 | var target = new AccountService(repository); 20 | 21 | var result = target.Create(new Person()); 22 | Assert.NotEmpty(result); 23 | } 24 | 25 | [Fact] 26 | public void Should_return_no_errors() 27 | { 28 | var repository = Substitute.For(); 29 | repository.Create(Arg.Any()); 30 | 31 | var newPerson = new Person { 32 | FirstName = "Jane", 33 | LastName = "Doe", 34 | EmailAddress = "Jane.Doe@gmail.com" 35 | }; 36 | 37 | var target = new AccountService(repository); 38 | 39 | var result = target.Create(newPerson); 40 | 41 | Assert.Empty(result); 42 | 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/TraditionalLayering/TraditionalLayering_Tests/When_getting_a_user.cs: -------------------------------------------------------------------------------- 1 | using NSubstitute; 2 | using TraditionalLayering; 3 | using TraditionalLayering.Model; 4 | using TraditionalLayering.Repository; 5 | using TraditionalLayering.Service; 6 | using Xunit; 7 | 8 | namespace TraditionalLayering_Tests 9 | { 10 | public class When_getting_a_user 11 | { 12 | [Fact] 13 | public void Should_return_null_if_no_user_found() 14 | { 15 | var repository = Substitute.For(); 16 | repository.GetLoggedInUser(2).Returns(x => null); 17 | 18 | var target = new AccountService(repository); 19 | 20 | var user = target.GetLoggedInUser(2); 21 | 22 | Assert.Null(user); 23 | } 24 | 25 | [Fact] 26 | public void Should_return_user() { 27 | var repository = Substitute.For(); 28 | repository.GetLoggedInUser(1).Returns( 29 | new Person { 30 | Id = 1, 31 | FirstName = "John", 32 | LastName = "Smith", 33 | EmailAddress = "john.smith@gmail.com" 34 | }); 35 | 36 | var target = new AccountService(repository); 37 | 38 | var user = target.GetLoggedInUser(1); 39 | 40 | Assert.Equal("John", user.FirstName); 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/TraditionalLayering/TraditionalLayering_Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | --------------------------------------------------------------------------------