├── .DS_Store ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── Finance.sln ├── images └── swagger.png ├── readme.md ├── source ├── .DS_Store ├── Dockerfile ├── MyWallet.Application │ ├── .DS_Store │ ├── AccountNotFoundException.cs │ ├── ApplicationException.cs │ ├── CustomerNotFoundException.cs │ ├── MyWallet.Application.csproj │ ├── Repositories │ │ ├── IAccountReadOnlyRepository.cs │ │ ├── IAccountWriteOnlyRepository.cs │ │ ├── ICustomerReadOnlyRepository.cs │ │ └── ICustomerWriteOnlyRepository.cs │ └── UseCases │ │ ├── AccountOutput.cs │ │ ├── CloseAccount │ │ ├── CloseAccountUseCase.cs │ │ └── ICloseAccountUseCase.cs │ │ ├── CustomerOutput.cs │ │ ├── Deposit │ │ ├── DepositOutput.cs │ │ ├── DepositUseCase.cs │ │ └── IDepositUseCase.cs │ │ ├── GetAccountDetails │ │ ├── GetAccountDetailsUseCase.cs │ │ └── IGetAccountDetailsUseCase.cs │ │ ├── GetCustomerDetails │ │ ├── GetCustomerDetailsUseCase.cs │ │ └── IGetCustomerDetailsUseCase.cs │ │ ├── Register │ │ ├── IRegisterUseCase.cs │ │ ├── RegisterOutput.cs │ │ └── RegisterUseCase.cs │ │ ├── TransactionOutput.cs │ │ └── Withdraw │ │ ├── IWithdrawUseCase.cs │ │ ├── WithdrawOutput.cs │ │ └── WithdrawUseCase.cs ├── MyWallet.Domain │ ├── .DS_Store │ ├── Accounts │ │ ├── Account.cs │ │ ├── AccountCannotBeClosedException.cs │ │ ├── Credit.cs │ │ ├── Debit.cs │ │ ├── ITransaction.cs │ │ ├── InsuficientFundsException.cs │ │ └── TransactionCollection.cs │ ├── Customers │ │ ├── AccountCollection.cs │ │ └── Customer.cs │ ├── DomainException.cs │ ├── IAggregateRoot.cs │ ├── IEntity.cs │ ├── MyWallet.Domain.csproj │ └── ValueObjects │ │ ├── Amount.cs │ │ ├── AmountShouldBePositiveException.cs │ │ ├── InvalidPersonnummerException.cs │ │ ├── Name.cs │ │ ├── NameShouldNotBeEmptyException.cs │ │ ├── Personnummer.cs │ │ └── PersonnummerShouldNotBeEmptyException.cs ├── MyWallet.Infrastructure │ ├── .DS_Store │ ├── AccountNotFoundException.cs │ ├── ApplicationModule.cs │ ├── CustomerNotFoundException.cs │ ├── EntityFrameworkDataAccess │ │ ├── .DS_Store │ │ ├── ContextFactory.cs │ │ ├── Entities │ │ │ ├── Account.cs │ │ │ ├── Credit.cs │ │ │ ├── Customer.cs │ │ │ └── Debit.cs │ │ ├── EntityFrameworkModule.cs │ │ ├── FinanceContext.cs │ │ ├── Migrations │ │ │ ├── 20181111112931_InitialCreate.Designer.cs │ │ │ ├── 20181111112931_InitialCreate.cs │ │ │ └── FinanceContextModelSnapshot.cs │ │ └── Repositories │ │ │ ├── AccountRepository.cs │ │ │ └── CustomerRepository.cs │ ├── InMemoryDataAccess │ │ ├── FinanceContext.cs │ │ ├── InMemoryModule.cs │ │ └── Repositories │ │ │ ├── AccountRepository.cs │ │ │ └── CustomerRepository.cs │ ├── InfrastructureException.cs │ └── MyWallet.Infrastructure.csproj ├── MyWallet.WebApi │ ├── .DS_Store │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── Filters │ │ ├── DomainExceptionFilter.cs │ │ └── ValidateModelAttribute.cs │ ├── MyWallet.WebApi.csproj │ ├── Program.cs │ ├── Startup.cs │ ├── UseCases │ │ ├── AccountDetailsModel.cs │ │ ├── CloseAccount │ │ │ ├── AccountsController.cs │ │ │ └── Presenter.cs │ │ ├── CustomerDetailsModel.cs │ │ ├── Deposit │ │ │ ├── AccountsController.cs │ │ │ ├── CurrentAccountBalanceModel.cs │ │ │ ├── DepositRequest.cs │ │ │ └── Presenter.cs │ │ ├── GetAccountDetails │ │ │ ├── AccountsController.cs │ │ │ └── Presenter.cs │ │ ├── GetCustomerDetails │ │ │ ├── CustomersController.cs │ │ │ └── Presenter.cs │ │ ├── Register │ │ │ ├── CustomerModel.cs │ │ │ ├── CustomersController.cs │ │ │ ├── Presenter.cs │ │ │ └── RegisterRequest.cs │ │ ├── TransactionModel.cs │ │ └── Withdraw │ │ │ ├── AccountsController.cs │ │ │ ├── CurrentBalanceModel.cs │ │ │ ├── Presenter.cs │ │ │ └── WithdrawRequest.cs │ ├── WebApiModule.cs │ ├── appsettings.json │ ├── autofac.json │ └── autofac.production.json └── docker-compose.yml └── tests ├── .DS_Store ├── MyWallet.Domain.Tests ├── .DS_Store ├── AccountTests.cs ├── AmountTests.cs ├── CreditTests.cs ├── CustomerTests.cs ├── DebitTests.cs ├── MyWallet.Domain.Tests.csproj ├── NameTests.cs ├── SSNTests.cs └── TransactionCollectionTests.cs ├── MyWallet.UseCases.Tests ├── .DS_Store ├── AccountTests.cs ├── CustomerTests.cs └── MyWallet.UseCases.Tests.csproj └── MyWallet.WebApi.Tests ├── .DS_Store ├── CustomerRegistration.cs ├── MyWallet.WebApi.Tests.csproj ├── autofac.json └── autofac.production.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivanpaulovich/clean-architecture-webapi-ef-core/6d3ac936bb87a94cc926a9730e93082d2ba5acee/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/tests/MyWallet.Domain.Tests/bin/Debug/netcoreapp2.0/MyWallet.Domain.Tests.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/tests/MyWallet.Domain.Tests", 16 | // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window 17 | "console": "internalConsole", 18 | "stopAtEntry": false, 19 | "internalConsoleOptions": "openOnSessionStart" 20 | }, 21 | { 22 | "name": ".NET Core Attach", 23 | "type": "coreclr", 24 | "request": "attach", 25 | "processId": "${command:pickProcess}" 26 | } 27 | ,] 28 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/tests/MyWallet.Domain.Tests/MyWallet.Domain.Tests.csproj" 11 | ], 12 | "problemMatcher": "$msCompile" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /Finance.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2026 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "source", "source", "{562425D1-9333-402C-8A08-EC58E8485845}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyWallet.Application", "source\MyWallet.Application\MyWallet.Application.csproj", "{53B86A45-8081-43A8-B818-80AC913789BC}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyWallet.Domain", "source\MyWallet.Domain\MyWallet.Domain.csproj", "{9C475BB7-18D1-475D-AD4A-884EF25ACFD9}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyWallet.Infrastructure", "source\MyWallet.Infrastructure\MyWallet.Infrastructure.csproj", "{3F003977-C9D3-4BC4-9345-CAE46412DD55}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{11965B7C-D871-47AB-9C39-5356EC795036}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyWallet.Domain.Tests", "tests\MyWallet.Domain.Tests\MyWallet.Domain.Tests.csproj", "{5FE45555-0005-4B72-B59B-07806D382418}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyWallet.UseCases.Tests", "tests\MyWallet.UseCases.Tests\MyWallet.UseCases.Tests.csproj", "{1E4038A6-E535-474F-AA20-D417E3C1FF41}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyWallet.WebApi", "source\MyWallet.WebApi\MyWallet.WebApi.csproj", "{93993FC5-91AF-4D55-A1B9-AA490F306F6C}" 21 | EndProject 22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyWallet.WebApi.Tests", "tests\MyWallet.WebApi.Tests\MyWallet.WebApi.Tests.csproj", "{7AB9308D-0EBE-45D0-859F-9414988230BA}" 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {53B86A45-8081-43A8-B818-80AC913789BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {53B86A45-8081-43A8-B818-80AC913789BC}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {53B86A45-8081-43A8-B818-80AC913789BC}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {53B86A45-8081-43A8-B818-80AC913789BC}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {9C475BB7-18D1-475D-AD4A-884EF25ACFD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {9C475BB7-18D1-475D-AD4A-884EF25ACFD9}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {9C475BB7-18D1-475D-AD4A-884EF25ACFD9}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {9C475BB7-18D1-475D-AD4A-884EF25ACFD9}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {3F003977-C9D3-4BC4-9345-CAE46412DD55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {3F003977-C9D3-4BC4-9345-CAE46412DD55}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {3F003977-C9D3-4BC4-9345-CAE46412DD55}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {3F003977-C9D3-4BC4-9345-CAE46412DD55}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {5FE45555-0005-4B72-B59B-07806D382418}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {5FE45555-0005-4B72-B59B-07806D382418}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {5FE45555-0005-4B72-B59B-07806D382418}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {5FE45555-0005-4B72-B59B-07806D382418}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {1E4038A6-E535-474F-AA20-D417E3C1FF41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {1E4038A6-E535-474F-AA20-D417E3C1FF41}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {1E4038A6-E535-474F-AA20-D417E3C1FF41}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {1E4038A6-E535-474F-AA20-D417E3C1FF41}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {93993FC5-91AF-4D55-A1B9-AA490F306F6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {93993FC5-91AF-4D55-A1B9-AA490F306F6C}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {93993FC5-91AF-4D55-A1B9-AA490F306F6C}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {93993FC5-91AF-4D55-A1B9-AA490F306F6C}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {7AB9308D-0EBE-45D0-859F-9414988230BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {7AB9308D-0EBE-45D0-859F-9414988230BA}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {7AB9308D-0EBE-45D0-859F-9414988230BA}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {7AB9308D-0EBE-45D0-859F-9414988230BA}.Release|Any CPU.Build.0 = Release|Any CPU 58 | EndGlobalSection 59 | GlobalSection(SolutionProperties) = preSolution 60 | HideSolutionNode = FALSE 61 | EndGlobalSection 62 | GlobalSection(ExtensibilityGlobals) = postSolution 63 | SolutionGuid = {DC349723-BBD9-489D-AD16-3840B6FF197F} 64 | EndGlobalSection 65 | GlobalSection(NestedProjects) = preSolution 66 | {53B86A45-8081-43A8-B818-80AC913789BC} = {562425D1-9333-402C-8A08-EC58E8485845} 67 | {9C475BB7-18D1-475D-AD4A-884EF25ACFD9} = {562425D1-9333-402C-8A08-EC58E8485845} 68 | {3F003977-C9D3-4BC4-9345-CAE46412DD55} = {562425D1-9333-402C-8A08-EC58E8485845} 69 | {5FE45555-0005-4B72-B59B-07806D382418} = {11965B7C-D871-47AB-9C39-5356EC795036} 70 | {1E4038A6-E535-474F-AA20-D417E3C1FF41} = {11965B7C-D871-47AB-9C39-5356EC795036} 71 | {93993FC5-91AF-4D55-A1B9-AA490F306F6C} = {562425D1-9333-402C-8A08-EC58E8485845} 72 | {7AB9308D-0EBE-45D0-859F-9414988230BA} = {11965B7C-D871-47AB-9C39-5356EC795036} 73 | EndGlobalSection 74 | EndGlobal 75 | -------------------------------------------------------------------------------- /images/swagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivanpaulovich/clean-architecture-webapi-ef-core/6d3ac936bb87a94cc926a9730e93082d2ba5acee/images/swagger.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # [![Try in PWD](https://raw.githubusercontent.com/play-with-docker/stacks/master/assets/images/button.png)](https://labs.play-with-docker.com/?stack=https://raw.githubusercontent.com/ivanpaulovich/clean-architecture-webapi-ef-core/master/source/docker-compose.yml&stack_name=clean-architecture-webapi-ef-core) Clean Architecture Implementation of a Personal Wallet Web Api 2 | 3 | The simplest demo on how to implement a Web Api using .NET Core and Entity Framework that protects the business rules from framework dependencies by following the Clean Architecture Principles. 4 | 5 | ## :whale: Running From The Docker Image 6 | 7 | ```sh 8 | docker run -e ASPNETCORE_ENVIRONMENT="Development" -p 5500:80 ivanpaulovich/clean-architecture-webapi-ef-core 9 | ``` 10 | 11 | ## :rocket: Running From Source 12 | 13 | To run on top of a InMemory persistance layer simple run: 14 | 15 | ```sh 16 | dotnet run --environment="dev" --project source/MyWallet.WebApi/MyWallet.WebApi.csproj 17 | ``` 18 | 19 | To run on top of a SQL Server persistance layer you need to setup the SQL Server database in steps ahead then run: 20 | 21 | ```sh 22 | dotnet run --environment="production" --project source/MyWallet.WebApi/MyWallet.WebApi.csproj 23 | ``` 24 | 25 | Then navigate to the Swagger URL `http://localhost:5500/` or run in command-line: 26 | 27 | ```sh 28 | curl -X POST "http://localhost:5500/api/Customers" -H "accept: application/json" -H "Content-Type: application/json-patch+json" -d "{ \"personnummer\": \"198608178877\", \"name\": \"string\", \"initialAmount\": 440}" 29 | ``` 30 | 31 | ## :floppy_disk: SQL Server Database 32 | 33 | If you wanna use Entity Framework, setup the SQL Server then update the database via dotnet EF Tool. 34 | 35 | ### Update the Database 36 | 37 | ```sh 38 | dotnet ef database update --project source/MyWallet.Infrastructure --startup-project source/MyWallet.WebApi 39 | ``` 40 | 41 | ### Add Migration 42 | 43 | Run the EF Tool to add a migration to the `MyWallet.Infrastructure` project. 44 | 45 | ```sh 46 | dotnet ef migrations add "InitialCreate" -o "EntityFrameworkDataAccess/Migrations" --project source/MyWallet.Infrastructure --startup-project source/MyWallet.WebApi 47 | ``` 48 | 49 | ### Setup the SQL Server in Docker 50 | 51 | To run SQL Server container images with Docker use: 52 | 53 | ```sh 54 | #!/bin/bash 55 | sudo docker pull mcr.microsoft.com/mssql/server:2017-latest 56 | sudo docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=' -p 1433:1433 --name sql1 -d mcr.microsoft.com/mssql/server:2017-latest 57 | sudo docker exec -it sql1 /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P '' -Q 'ALTER LOGIN SA WITH PASSWORD=""' 58 | ``` 59 | 60 | It will enable a SQL Server running on `Server=localhost;User Id=sa;Password=;` for more details checkout the docs at [How to run a SQL Server in a Docker Container](https://docs.microsoft.com/en-us/sql/linux/quickstart-install-connect-docker?view=sql-server-2017). 61 | 62 | ## :checkered_flag: Requirements 63 | 64 | Developed and Tested using: 65 | 66 | * MacOS Sierra 67 | * VSCode :heart: 68 | * [.NET SDK 2.2](https://www.microsoft.com/net/download/dotnet-core/2.2). 69 | * Docker :whale: 70 | * SQL Server via Docker container. 71 | 72 | ## :telephone: For Support and Issues 73 | 74 | I am happy to be reach out through the **Issues Tab**. 75 | -------------------------------------------------------------------------------- /source/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivanpaulovich/clean-architecture-webapi-ef-core/6d3ac936bb87a94cc926a9730e93082d2ba5acee/source/.DS_Store -------------------------------------------------------------------------------- /source/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base 2 | WORKDIR /app 3 | EXPOSE 80 4 | # 5 | # 6 | FROM microsoft/dotnet:2.1-sdk AS builder 7 | WORKDIR /source 8 | COPY . . 9 | WORKDIR /source/MyWallet.Infrastructure 10 | RUN dotnet build -c Release 11 | WORKDIR /source/MyWallet.WebApi 12 | RUN dotnet build -c Release -o /app 13 | # 14 | # 15 | FROM builder AS publish 16 | RUN dotnet publish -c Release -o /app 17 | # 18 | # 19 | FROM base AS production 20 | WORKDIR /app 21 | COPY --from=publish /app . 22 | ENTRYPOINT ["dotnet", "MyWallet.WebApi.dll"] -------------------------------------------------------------------------------- /source/MyWallet.Application/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivanpaulovich/clean-architecture-webapi-ef-core/6d3ac936bb87a94cc926a9730e93082d2ba5acee/source/MyWallet.Application/.DS_Store -------------------------------------------------------------------------------- /source/MyWallet.Application/AccountNotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application { 2 | internal sealed class AccountNotFoundException : ApplicationException { 3 | internal AccountNotFoundException (string message) : base (message) { } 4 | } 5 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/ApplicationException.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application { 2 | using System; 3 | public class ApplicationException : Exception { 4 | internal ApplicationException (string businessMessage) : base (businessMessage) { } 5 | } 6 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/CustomerNotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application { 2 | internal sealed class CustomerNotFoundException : ApplicationException { 3 | internal CustomerNotFoundException (string message) : base (message) { } 4 | } 5 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/MyWallet.Application.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | full 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /source/MyWallet.Application/Repositories/IAccountReadOnlyRepository.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.Repositories { 2 | using System.Threading.Tasks; 3 | using System; 4 | using MyWallet.Domain.Accounts; 5 | 6 | public interface IAccountReadOnlyRepository { 7 | Task Get (Guid id); 8 | } 9 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/Repositories/IAccountWriteOnlyRepository.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.Repositories { 2 | using System.Threading.Tasks; 3 | using MyWallet.Domain.Accounts; 4 | 5 | public interface IAccountWriteOnlyRepository { 6 | Task Add (Account account, Credit credit); 7 | Task Update (Account account, Credit credit); 8 | Task Update (Account account, Debit debit); 9 | Task Delete (Account account); 10 | } 11 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/Repositories/ICustomerReadOnlyRepository.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.Repositories { 2 | using System.Threading.Tasks; 3 | using System; 4 | using MyWallet.Domain.Customers; 5 | 6 | public interface ICustomerReadOnlyRepository { 7 | Task Get (Guid id); 8 | } 9 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/Repositories/ICustomerWriteOnlyRepository.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.Repositories { 2 | using System.Threading.Tasks; 3 | using MyWallet.Domain.Customers; 4 | 5 | public interface ICustomerWriteOnlyRepository { 6 | Task Add (Customer customer); 7 | Task Update (Customer customer); 8 | } 9 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/UseCases/AccountOutput.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.UseCases { 2 | using System.Collections.Generic; 3 | using System; 4 | using MyWallet.Domain.Accounts; 5 | 6 | public sealed class AccountOutput { 7 | public Guid AccountId { get; } 8 | public decimal CurrentBalance { get; } 9 | public List Transactions { get; } 10 | 11 | public AccountOutput ( 12 | Guid accountId, 13 | decimal currentBalance, 14 | List transactions) { 15 | AccountId = accountId; 16 | CurrentBalance = currentBalance; 17 | Transactions = transactions; 18 | } 19 | 20 | public AccountOutput (Account account) { 21 | AccountId = account.Id; 22 | CurrentBalance = account.GetCurrentBalance (); 23 | 24 | List transactionResults = new List (); 25 | foreach (ITransaction transaction in account.Transactions.ToReadOnlyCollection ()) { 26 | TransactionOutput transactionOutput = new TransactionOutput ( 27 | transaction.Description, transaction.Amount, transaction.TransactionDate); 28 | transactionResults.Add (transactionOutput); 29 | } 30 | 31 | Transactions = transactionResults; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/UseCases/CloseAccount/CloseAccountUseCase.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.UseCases.CloseAccount { 2 | using System.Threading.Tasks; 3 | using System; 4 | using MyWallet.Application.Repositories; 5 | using MyWallet.Domain.Accounts; 6 | 7 | public sealed class CloseAccountUseCase : ICloseAccountUseCase { 8 | private readonly IAccountReadOnlyRepository _accountReadOnlyRepository; 9 | private readonly IAccountWriteOnlyRepository _accountWriteOnlyRepository; 10 | 11 | public CloseAccountUseCase ( 12 | IAccountReadOnlyRepository accountReadOnlyRepository, 13 | IAccountWriteOnlyRepository accountWriteOnlyRepository) { 14 | _accountReadOnlyRepository = accountReadOnlyRepository; 15 | _accountWriteOnlyRepository = accountWriteOnlyRepository; 16 | } 17 | 18 | public async Task Execute (Guid accountId) { 19 | Account account = await _accountReadOnlyRepository.Get (accountId); 20 | if (account == null) 21 | throw new AccountNotFoundException ($"The account {accountId} does not exists or is already closed."); 22 | 23 | account.Close (); 24 | 25 | await _accountWriteOnlyRepository.Delete (account); 26 | 27 | return account.Id; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/UseCases/CloseAccount/ICloseAccountUseCase.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.UseCases.CloseAccount { 2 | using System.Threading.Tasks; 3 | using System; 4 | 5 | public interface ICloseAccountUseCase { 6 | Task Execute (Guid accountId); 7 | } 8 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/UseCases/CustomerOutput.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.UseCases { 2 | using System.Collections.Generic; 3 | using System; 4 | using MyWallet.Domain.Customers; 5 | 6 | public sealed class CustomerOutput { 7 | public Guid CustomerId { get; } 8 | public string Personnummer { get; } 9 | public string Name { get; } 10 | public IReadOnlyList Accounts { get; } 11 | 12 | public CustomerOutput ( 13 | Customer customer, 14 | List accounts) { 15 | CustomerId = customer.Id; 16 | Personnummer = customer.SSN; 17 | Name = customer.Name; 18 | Accounts = accounts; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/UseCases/Deposit/DepositOutput.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.UseCases.Deposit { 2 | using MyWallet.Domain.Accounts; 3 | using MyWallet.Domain.ValueObjects; 4 | 5 | public sealed class DepositOutput { 6 | public TransactionOutput Transaction { get; } 7 | public decimal UpdatedBalance { get; } 8 | 9 | public DepositOutput ( 10 | Credit credit, 11 | Amount updatedBalance) { 12 | Transaction = new TransactionOutput ( 13 | credit.Description, 14 | credit.Amount, 15 | credit.TransactionDate); 16 | 17 | UpdatedBalance = updatedBalance; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/UseCases/Deposit/DepositUseCase.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.UseCases.Deposit { 2 | using System.Threading.Tasks; 3 | using System; 4 | using MyWallet.Application.Repositories; 5 | using MyWallet.Domain.Accounts; 6 | using MyWallet.Domain.ValueObjects; 7 | 8 | public sealed class DepositUseCase : IDepositUseCase { 9 | private readonly IAccountReadOnlyRepository _accountReadOnlyRepository; 10 | private readonly IAccountWriteOnlyRepository _accountWriteOnlyRepository; 11 | 12 | public DepositUseCase ( 13 | IAccountReadOnlyRepository accountReadOnlyRepository, 14 | IAccountWriteOnlyRepository accountWriteOnlyRepository) { 15 | _accountReadOnlyRepository = accountReadOnlyRepository; 16 | _accountWriteOnlyRepository = accountWriteOnlyRepository; 17 | } 18 | 19 | public async Task Execute (Guid accountId, Amount amount) { 20 | Account account = await _accountReadOnlyRepository.Get (accountId); 21 | if (account == null) 22 | throw new AccountNotFoundException ($"The account {accountId} does not exists or is already closed."); 23 | 24 | account.Deposit (amount); 25 | Credit credit = (Credit) account.GetLastTransaction (); 26 | 27 | await _accountWriteOnlyRepository.Update (account, credit); 28 | 29 | DepositOutput output = new DepositOutput ( 30 | credit, 31 | account.GetCurrentBalance ()); 32 | return output; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/UseCases/Deposit/IDepositUseCase.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.UseCases.Deposit { 2 | using System.Threading.Tasks; 3 | using System; 4 | using MyWallet.Domain.ValueObjects; 5 | 6 | public interface IDepositUseCase { 7 | Task Execute (Guid accountId, Amount amount); 8 | } 9 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/UseCases/GetAccountDetails/GetAccountDetailsUseCase.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.UseCases.GetAccountDetails { 2 | using System.Threading.Tasks; 3 | using System; 4 | using MyWallet.Application.Repositories; 5 | using MyWallet.Domain.Accounts; 6 | 7 | public sealed class GetAccountDetailsUseCase : IGetAccountDetailsUseCase { 8 | private readonly IAccountReadOnlyRepository _accountReadOnlyRepository; 9 | 10 | public GetAccountDetailsUseCase (IAccountReadOnlyRepository accountReadOnlyRepository) { 11 | _accountReadOnlyRepository = accountReadOnlyRepository; 12 | } 13 | 14 | public async Task Execute (Guid accountId) { 15 | Account account = await _accountReadOnlyRepository.Get (accountId); 16 | 17 | if (account == null) 18 | throw new AccountNotFoundException ($"The account {accountId} does not exists or is not processed yet."); 19 | 20 | AccountOutput output = new AccountOutput (account); 21 | return output; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/UseCases/GetAccountDetails/IGetAccountDetailsUseCase.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.UseCases.GetAccountDetails { 2 | using System.Threading.Tasks; 3 | using System; 4 | 5 | public interface IGetAccountDetailsUseCase { 6 | Task Execute (Guid accountId); 7 | } 8 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/UseCases/GetCustomerDetails/GetCustomerDetailsUseCase.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.UseCases.GetCustomerDetails { 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using System; 5 | using MyWallet.Application.Repositories; 6 | using MyWallet.Domain.Accounts; 7 | using MyWallet.Domain.Customers; 8 | 9 | public sealed class GetCustomerDetailsUseCase : IGetCustomerDetailsUseCase { 10 | private readonly ICustomerReadOnlyRepository _customerReadOnlyRepository; 11 | private readonly IAccountReadOnlyRepository _accountReadOnlyRepository; 12 | 13 | public GetCustomerDetailsUseCase ( 14 | ICustomerReadOnlyRepository customerReadOnlyRepository, 15 | IAccountReadOnlyRepository accountReadOnlyRepository) { 16 | _customerReadOnlyRepository = customerReadOnlyRepository; 17 | _accountReadOnlyRepository = accountReadOnlyRepository; 18 | } 19 | 20 | public async Task Execute (Guid customerId) { 21 | Customer customer = await _customerReadOnlyRepository.Get (customerId); 22 | 23 | if (customer == null) 24 | throw new CustomerNotFoundException ($"The customer {customerId} does not exists or is not processed yet."); 25 | 26 | List accounts = new List (); 27 | 28 | foreach (Guid accountId in customer.Accounts.ToReadOnlyCollection ()) { 29 | Account account = await _accountReadOnlyRepository.Get (accountId); 30 | 31 | if (account != null) { 32 | AccountOutput accountOutput = new AccountOutput (account); 33 | accounts.Add (accountOutput); 34 | } 35 | } 36 | 37 | CustomerOutput output = new CustomerOutput (customer, accounts); 38 | return output; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/UseCases/GetCustomerDetails/IGetCustomerDetailsUseCase.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.UseCases.GetCustomerDetails { 2 | using System.Threading.Tasks; 3 | using System; 4 | 5 | public interface IGetCustomerDetailsUseCase { 6 | Task Execute (Guid customerId); 7 | } 8 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/UseCases/Register/IRegisterUseCase.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.UseCases.Register { 2 | using System.Threading.Tasks; 3 | using MyWallet.Domain.ValueObjects; 4 | 5 | public interface IRegisterUseCase { 6 | Task Execute (Personnummer personnummer, Name name, Amount initialAmount); 7 | } 8 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/UseCases/Register/RegisterOutput.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.UseCases.Register { 2 | using System.Collections.Generic; 3 | using MyWallet.Domain.Accounts; 4 | using MyWallet.Domain.Customers; 5 | 6 | public sealed class RegisterOutput { 7 | public CustomerOutput Customer { get; } 8 | public AccountOutput Account { get; } 9 | 10 | public RegisterOutput (Customer customer, Account account) { 11 | List transactionOutputs = new List (); 12 | 13 | foreach (ITransaction transaction in account.Transactions.ToReadOnlyCollection ()) { 14 | transactionOutputs.Add ( 15 | new TransactionOutput ( 16 | transaction.Description, 17 | transaction.Amount, 18 | transaction.TransactionDate)); 19 | } 20 | 21 | Account = new AccountOutput (account.Id, account.GetCurrentBalance (), transactionOutputs); 22 | 23 | List accountOutputs = new List (); 24 | accountOutputs.Add (Account); 25 | 26 | Customer = new CustomerOutput (customer, accountOutputs); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/UseCases/Register/RegisterUseCase.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.UseCases.Register { 2 | using System.Threading.Tasks; 3 | using MyWallet.Application.Repositories; 4 | using MyWallet.Domain.Accounts; 5 | using MyWallet.Domain.Customers; 6 | using MyWallet.Domain.ValueObjects; 7 | 8 | public sealed class RegisterUseCase : IRegisterUseCase { 9 | private readonly ICustomerWriteOnlyRepository _customerWriteOnlyRepository; 10 | private readonly IAccountWriteOnlyRepository _accountWriteOnlyRepository; 11 | 12 | public RegisterUseCase ( 13 | ICustomerWriteOnlyRepository customerWriteOnlyRepository, 14 | IAccountWriteOnlyRepository accountWriteOnlyRepository) { 15 | _customerWriteOnlyRepository = customerWriteOnlyRepository; 16 | _accountWriteOnlyRepository = accountWriteOnlyRepository; 17 | } 18 | 19 | public async Task Execute (Personnummer personnummer, Name name, Amount initialAmount) { 20 | Customer customer = new Customer (personnummer, name); 21 | 22 | Account account = new Account (customer.Id); 23 | account.Deposit (initialAmount); 24 | Credit credit = (Credit) account.GetLastTransaction (); 25 | 26 | customer.Register (account.Id); 27 | 28 | await _customerWriteOnlyRepository.Add (customer); 29 | await _accountWriteOnlyRepository.Add (account, credit); 30 | 31 | RegisterOutput output = new RegisterOutput (customer, account); 32 | return output; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/UseCases/TransactionOutput.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.UseCases { 2 | using System; 3 | public sealed class TransactionOutput { 4 | public string Description { get; } 5 | public decimal Amount { get; } 6 | public DateTime TransactionDate { get; } 7 | 8 | public TransactionOutput ( 9 | string description, 10 | decimal amount, 11 | DateTime transactionDate) { 12 | Description = description; 13 | Amount = amount; 14 | TransactionDate = transactionDate; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/UseCases/Withdraw/IWithdrawUseCase.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.UseCases.Withdraw { 2 | using System.Threading.Tasks; 3 | using System; 4 | using MyWallet.Domain.ValueObjects; 5 | 6 | public interface IWithdrawUseCase { 7 | Task Execute (Guid accountId, Amount amount); 8 | } 9 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/UseCases/Withdraw/WithdrawOutput.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.UseCases.Withdraw { 2 | using MyWallet.Domain.Accounts; 3 | using MyWallet.Domain.ValueObjects; 4 | 5 | public sealed class WithdrawOutput { 6 | public TransactionOutput Transaction { get; } 7 | public decimal UpdatedBalance { get; } 8 | 9 | public WithdrawOutput (Debit transaction, Amount updatedBalance) { 10 | Transaction = new TransactionOutput ( 11 | transaction.Description, 12 | transaction.Amount, 13 | transaction.TransactionDate); 14 | 15 | UpdatedBalance = updatedBalance; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /source/MyWallet.Application/UseCases/Withdraw/WithdrawUseCase.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Application.UseCases.Withdraw { 2 | using System.Threading.Tasks; 3 | using System; 4 | using MyWallet.Application.Repositories; 5 | using MyWallet.Domain.Accounts; 6 | using MyWallet.Domain.ValueObjects; 7 | 8 | public sealed class WithdrawUseCase : IWithdrawUseCase { 9 | private readonly IAccountReadOnlyRepository _accountReadOnlyRepository; 10 | private readonly IAccountWriteOnlyRepository _accountWriteOnlyRepository; 11 | 12 | public WithdrawUseCase ( 13 | IAccountReadOnlyRepository accountReadOnlyRepository, 14 | IAccountWriteOnlyRepository accountWriteOnlyRepository) { 15 | _accountReadOnlyRepository = accountReadOnlyRepository; 16 | _accountWriteOnlyRepository = accountWriteOnlyRepository; 17 | } 18 | 19 | public async Task Execute (Guid accountId, Amount amount) { 20 | Account account = await _accountReadOnlyRepository.Get (accountId); 21 | if (account == null) 22 | throw new AccountNotFoundException ($"The account {accountId} does not exists or is already closed."); 23 | 24 | account.Withdraw (amount); 25 | Debit debit = (Debit) account.GetLastTransaction (); 26 | 27 | await _accountWriteOnlyRepository.Update (account, debit); 28 | 29 | WithdrawOutput output = new WithdrawOutput ( 30 | debit, 31 | account.GetCurrentBalance () 32 | ); 33 | 34 | return output; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /source/MyWallet.Domain/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivanpaulovich/clean-architecture-webapi-ef-core/6d3ac936bb87a94cc926a9730e93082d2ba5acee/source/MyWallet.Domain/.DS_Store -------------------------------------------------------------------------------- /source/MyWallet.Domain/Accounts/Account.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Domain.Accounts { 2 | using System; 3 | using MyWallet.Domain.ValueObjects; 4 | 5 | public sealed class Account : IEntity, IAggregateRoot { 6 | public Guid Id { get; private set; } 7 | public Guid CustomerId { get; private set; } 8 | public TransactionCollection Transactions { get; private set; } 9 | 10 | public Account (Guid customerId) { 11 | Id = Guid.NewGuid (); 12 | CustomerId = customerId; 13 | Transactions = new TransactionCollection (); 14 | } 15 | 16 | public void Deposit (Amount amount) { 17 | Credit credit = new Credit (Id, amount); 18 | Transactions.Add (credit); 19 | } 20 | 21 | public void Withdraw (Amount amount) { 22 | Amount balance = Transactions.GetBalance (); 23 | 24 | if (balance < amount) 25 | throw new InsuficientFundsException ( 26 | $"The account {Id} does not have enough funds to withdraw {amount}. Current Balance {balance}."); 27 | 28 | Debit debit = new Debit (Id, amount); 29 | Transactions.Add (debit); 30 | } 31 | 32 | public void Close () { 33 | Amount balance = Transactions.GetBalance (); 34 | 35 | if (balance > 0) 36 | throw new AccountCannotBeClosedException ( 37 | $"The account {Id} can not be closed because it has funds. Current Balance {balance}."); 38 | } 39 | 40 | public Amount GetCurrentBalance () { 41 | Amount balance = Transactions.GetBalance (); 42 | return balance; 43 | } 44 | 45 | public ITransaction GetLastTransaction () { 46 | ITransaction lastTransaction = Transactions.CopyOfLastTransaction (); 47 | return lastTransaction; 48 | } 49 | 50 | private Account () { } 51 | 52 | public static Account LoadFromDetails (Guid id, Guid customerId, TransactionCollection transactions) { 53 | Account account = new Account (); 54 | account.Id = id; 55 | account.CustomerId = customerId; 56 | account.Transactions = transactions; 57 | return account; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /source/MyWallet.Domain/Accounts/AccountCannotBeClosedException.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Domain.Accounts { 2 | public sealed class AccountCannotBeClosedException : DomainException { 3 | internal AccountCannotBeClosedException (string message) : base (message) { } 4 | } 5 | } -------------------------------------------------------------------------------- /source/MyWallet.Domain/Accounts/Credit.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Domain.Accounts { 2 | using System; 3 | using MyWallet.Domain.ValueObjects; 4 | 5 | public sealed class Credit : IEntity, ITransaction { 6 | public Guid Id { get; private set; } 7 | public Guid AccountId { get; private set; } 8 | public Amount Amount { get; private set; } 9 | public string Description { 10 | get { return "Credit"; } 11 | } 12 | public DateTime TransactionDate { get; private set; } 13 | 14 | private Credit () { } 15 | 16 | public static Credit LoadFromDetails (Guid id, Guid accountId, Amount amount, DateTime transactionDate) { 17 | Credit credit = new Credit (); 18 | credit.Id = id; 19 | credit.AccountId = accountId; 20 | credit.Amount = amount; 21 | credit.TransactionDate = transactionDate; 22 | return credit; 23 | } 24 | 25 | public Credit (Guid accountId, Amount amount) { 26 | Id = Guid.NewGuid (); 27 | AccountId = accountId; 28 | Amount = amount; 29 | TransactionDate = DateTime.UtcNow; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /source/MyWallet.Domain/Accounts/Debit.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Domain.Accounts { 2 | using System; 3 | using MyWallet.Domain.ValueObjects; 4 | 5 | public sealed class Debit : IEntity, ITransaction { 6 | public Guid Id { get; private set; } 7 | public Guid AccountId { get; private set; } 8 | public Amount Amount { get; private set; } 9 | public string Description { 10 | get { return "Debit"; } 11 | } 12 | public DateTime TransactionDate { get; private set; } 13 | 14 | private Debit () { } 15 | 16 | public static Debit LoadFromDetails (Guid id, Guid accountId, Amount amount, DateTime transactionDate) { 17 | Debit debit = new Debit (); 18 | debit.Id = id; 19 | debit.AccountId = accountId; 20 | debit.Amount = amount; 21 | debit.TransactionDate = transactionDate; 22 | return debit; 23 | } 24 | 25 | public Debit (Guid accountId, Amount amount) { 26 | Id = Guid.NewGuid (); 27 | AccountId = accountId; 28 | Amount = amount; 29 | TransactionDate = DateTime.UtcNow; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /source/MyWallet.Domain/Accounts/ITransaction.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Domain.Accounts { 2 | using System; 3 | using MyWallet.Domain.ValueObjects; 4 | 5 | public interface ITransaction { 6 | Amount Amount { get; } 7 | string Description { get; } 8 | DateTime TransactionDate { get; } 9 | } 10 | } -------------------------------------------------------------------------------- /source/MyWallet.Domain/Accounts/InsuficientFundsException.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Domain.Accounts { 2 | public sealed class InsuficientFundsException : DomainException { 3 | internal InsuficientFundsException (string message) : base (message) { } 4 | } 5 | } -------------------------------------------------------------------------------- /source/MyWallet.Domain/Accounts/TransactionCollection.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Domain.Accounts { 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using MyWallet.Domain.ValueObjects; 5 | 6 | public sealed class TransactionCollection { 7 | private readonly IList _transactions; 8 | 9 | public TransactionCollection () { 10 | _transactions = new List (); 11 | } 12 | 13 | public IReadOnlyCollection ToReadOnlyCollection () { 14 | IReadOnlyCollection transactions = new ReadOnlyCollection (_transactions); 15 | return transactions; 16 | } 17 | 18 | public ITransaction CopyOfLastTransaction () { 19 | ITransaction lastTransaction = _transactions[_transactions.Count - 1]; 20 | ITransaction copyOfLastTransaction = null; 21 | 22 | if (lastTransaction is Credit) { 23 | copyOfLastTransaction = Credit.LoadFromDetails ( 24 | ((Credit) lastTransaction).Id, 25 | ((Credit) lastTransaction).AccountId, 26 | ((Credit) lastTransaction).Amount, 27 | ((Credit) lastTransaction).TransactionDate 28 | ); 29 | } 30 | 31 | if (lastTransaction is Debit) { 32 | copyOfLastTransaction = Debit.LoadFromDetails ( 33 | ((Debit) lastTransaction).Id, 34 | ((Debit) lastTransaction).AccountId, 35 | ((Debit) lastTransaction).Amount, 36 | ((Debit) lastTransaction).TransactionDate 37 | ); 38 | } 39 | 40 | return copyOfLastTransaction; 41 | } 42 | 43 | public void Add (ITransaction transaction) { 44 | _transactions.Add (transaction); 45 | } 46 | 47 | public void Add (IEnumerable transactions) { 48 | foreach (var transaction in transactions) { 49 | Add (transaction); 50 | } 51 | } 52 | 53 | public Amount GetBalance () { 54 | Amount balance = 0; 55 | 56 | foreach (ITransaction item in _transactions) { 57 | if (item is Debit) 58 | balance = balance - item.Amount; 59 | 60 | if (item is Credit) 61 | balance = balance + item.Amount; 62 | } 63 | 64 | return balance; 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /source/MyWallet.Domain/Customers/AccountCollection.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Domain.Customers { 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System; 5 | 6 | public sealed class AccountCollection { 7 | private readonly IList _accountIds; 8 | 9 | public AccountCollection () { 10 | _accountIds = new List (); 11 | } 12 | 13 | public IReadOnlyCollection ToReadOnlyCollection () { 14 | IReadOnlyCollection accountIds = new ReadOnlyCollection (_accountIds); 15 | return accountIds; 16 | } 17 | 18 | public void Add (Guid accountId) { 19 | _accountIds.Add (accountId); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /source/MyWallet.Domain/Customers/Customer.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Domain.Customers { 2 | using System; 3 | using MyWallet.Domain.ValueObjects; 4 | 5 | public sealed class Customer : IEntity, IAggregateRoot { 6 | public Guid Id { get; private set; } 7 | public Name Name { get; private set; } 8 | public Personnummer SSN { get; private set; } 9 | public AccountCollection Accounts { get; private set; } 10 | 11 | public Customer (Personnummer ssn, Name name) { 12 | Id = Guid.NewGuid (); 13 | SSN = ssn; 14 | Name = name; 15 | Accounts = new AccountCollection (); 16 | } 17 | 18 | public void Register (Guid accountId) { 19 | Accounts.Add (accountId); 20 | } 21 | 22 | private Customer () { } 23 | 24 | public static Customer LoadFromDetails (Guid id, Name name, Personnummer ssn, AccountCollection accounts) { 25 | Customer customer = new Customer (); 26 | customer.Id = id; 27 | customer.Name = name; 28 | customer.SSN = ssn; 29 | customer.Accounts = accounts; 30 | return customer; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /source/MyWallet.Domain/DomainException.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Domain { 2 | using System; 3 | 4 | public class DomainException : Exception { 5 | internal DomainException (string businessMessage) : base (businessMessage) { } 6 | } 7 | } -------------------------------------------------------------------------------- /source/MyWallet.Domain/IAggregateRoot.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Domain { 2 | internal interface IAggregateRoot : IEntity { } 3 | } -------------------------------------------------------------------------------- /source/MyWallet.Domain/IEntity.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Domain { 2 | using System; 3 | 4 | internal interface IEntity { 5 | Guid Id { get; } 6 | } 7 | } -------------------------------------------------------------------------------- /source/MyWallet.Domain/MyWallet.Domain.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | full 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /source/MyWallet.Domain/ValueObjects/Amount.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Domain.ValueObjects { 2 | public sealed class Amount { 3 | private decimal _value; 4 | 5 | public Amount (decimal value) { 6 | if (value < 0) 7 | throw new AmountShouldBePositiveException ($"The {value} its not a valid amount. It should be a positive value."); 8 | 9 | _value = value; 10 | } 11 | 12 | public override string ToString () { 13 | return _value.ToString ("C"); 14 | } 15 | 16 | public static implicit operator decimal (Amount value) { 17 | return value._value; 18 | } 19 | 20 | public static implicit operator Amount (decimal value) { 21 | return new Amount (value); 22 | } 23 | 24 | public static Amount operator + (Amount amount1, Amount amount2) { 25 | return new Amount (amount1._value + amount2._value); 26 | } 27 | 28 | public static Amount operator - (Amount amount1, Amount amount2) { 29 | return new Amount (amount1._value - amount2._value); 30 | } 31 | 32 | public static bool operator < (Amount amount1, Amount amount2) { 33 | return amount1._value < amount2._value; 34 | } 35 | 36 | public static bool operator > (Amount amount1, Amount amount2) { 37 | return amount1._value > amount2._value; 38 | } 39 | 40 | public static bool operator <= (Amount amount1, Amount amount2) { 41 | return amount1._value <= amount2._value; 42 | } 43 | 44 | public static bool operator >= (Amount amount1, Amount amount2) { 45 | return amount1._value >= amount2._value; 46 | } 47 | 48 | public override bool Equals (object obj) { 49 | if (ReferenceEquals (null, obj)) { 50 | return false; 51 | } 52 | 53 | if (ReferenceEquals (this, obj)) { 54 | return true; 55 | } 56 | 57 | if (obj is decimal) { 58 | return (decimal) obj == _value; 59 | } 60 | 61 | return ((Amount) obj)._value == _value; 62 | } 63 | 64 | public override int GetHashCode () { 65 | return -1939223833 + _value.GetHashCode (); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /source/MyWallet.Domain/ValueObjects/AmountShouldBePositiveException.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Domain.ValueObjects { 2 | public sealed class AmountShouldBePositiveException : DomainException { 3 | internal AmountShouldBePositiveException (string message) : base (message) { } 4 | } 5 | } -------------------------------------------------------------------------------- /source/MyWallet.Domain/ValueObjects/InvalidPersonnummerException.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Domain.ValueObjects { 2 | internal sealed class InvalidPersonnummerException : DomainException { 3 | internal InvalidPersonnummerException (string message) : base (message) { } 4 | } 5 | } -------------------------------------------------------------------------------- /source/MyWallet.Domain/ValueObjects/Name.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Domain.ValueObjects { 2 | public sealed class Name { 3 | private string _text; 4 | 5 | public Name (string text) { 6 | if (string.IsNullOrWhiteSpace (text)) 7 | throw new NameShouldNotBeEmptyException ("The 'Name' field is required. Supplied an empty value."); 8 | 9 | _text = text; 10 | } 11 | 12 | public static implicit operator Name (string text) { 13 | return new Name (text); 14 | } 15 | 16 | public static implicit operator string (Name name) { 17 | return name._text; 18 | } 19 | 20 | public override bool Equals (object obj) { 21 | if (ReferenceEquals (null, obj)) { 22 | return false; 23 | } 24 | 25 | if (ReferenceEquals (this, obj)) { 26 | return true; 27 | } 28 | 29 | if (obj is string) { 30 | return obj.ToString () == _text; 31 | } 32 | 33 | return ((Name) obj)._text == _text; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /source/MyWallet.Domain/ValueObjects/NameShouldNotBeEmptyException.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Domain.ValueObjects { 2 | public sealed class NameShouldNotBeEmptyException : DomainException { 3 | internal NameShouldNotBeEmptyException (string message) : base (message) { } 4 | } 5 | } -------------------------------------------------------------------------------- /source/MyWallet.Domain/ValueObjects/Personnummer.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Domain.ValueObjects { 2 | using System.Text.RegularExpressions; 3 | 4 | public sealed class Personnummer { 5 | private string _text; 6 | const string RegExForValidation = @"^\d{6,8}[-|(\s)]{0,1}\d{4}$"; 7 | 8 | public Personnummer (string text) { 9 | if (string.IsNullOrWhiteSpace (text)) 10 | throw new PersonnummerShouldNotBeEmptyException ("The 'Personnummer' field is required"); 11 | 12 | Regex regex = new Regex (RegExForValidation); 13 | Match match = regex.Match (text); 14 | 15 | if (!match.Success) 16 | throw new InvalidPersonnummerException ($"{text} its an invalid Personnummer value. Use the format YYMMDDNNNN."); 17 | 18 | _text = text; 19 | } 20 | 21 | public static implicit operator Personnummer (string text) { 22 | return new Personnummer (text); 23 | } 24 | 25 | public static implicit operator string (Personnummer ssn) { 26 | return ssn._text; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /source/MyWallet.Domain/ValueObjects/PersonnummerShouldNotBeEmptyException.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Domain.ValueObjects { 2 | public sealed class PersonnummerShouldNotBeEmptyException : DomainException { 3 | internal PersonnummerShouldNotBeEmptyException (string message) : base (message) { } 4 | } 5 | } -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivanpaulovich/clean-architecture-webapi-ef-core/6d3ac936bb87a94cc926a9730e93082d2ba5acee/source/MyWallet.Infrastructure/.DS_Store -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/AccountNotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Infrastructure { 2 | public class AccountNotFoundException : InfrastructureException { 3 | internal AccountNotFoundException (string message) : base (message) { } 4 | } 5 | } -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/ApplicationModule.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Infrastructure { 2 | using Autofac; 3 | using MyWallet.Application; 4 | 5 | public class ApplicationModule : Module { 6 | protected override void Load (ContainerBuilder builder) { 7 | // 8 | // Register all Types in MyWallet.Application 9 | builder.RegisterAssemblyTypes (typeof (ApplicationException).Assembly) 10 | .AsImplementedInterfaces () 11 | .InstancePerLifetimeScope (); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/CustomerNotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Infrastructure { 2 | public class CustomerNotFoundException : InfrastructureException { 3 | internal CustomerNotFoundException (string message) : base (message) { } 4 | } 5 | } -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/EntityFrameworkDataAccess/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivanpaulovich/clean-architecture-webapi-ef-core/6d3ac936bb87a94cc926a9730e93082d2ba5acee/source/MyWallet.Infrastructure/EntityFrameworkDataAccess/.DS_Store -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/EntityFrameworkDataAccess/ContextFactory.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Infrastructure.EntityFrameworkDataAccess { 2 | using System.IO; 3 | using Microsoft.EntityFrameworkCore.Design; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.Extensions.Configuration; 7 | 8 | public sealed class ContextFactory : IDesignTimeDbContextFactory { 9 | public MyWalletContext CreateDbContext (string[] args) { 10 | IConfigurationRoot configuration = new ConfigurationBuilder() 11 | .SetBasePath(Directory.GetCurrentDirectory()) 12 | .AddJsonFile("autofac.production.json") 13 | .Build(); 14 | 15 | DbContextOptionsBuilder builder = new DbContextOptionsBuilder(); 16 | string connectionString = configuration 17 | .GetValue("modules:2:properties:ConnectionString"); // this value is in the file MyWallet.WebApi/autofac.production.json 18 | 19 | System.Console.WriteLine($"Using the Connectionstring `{connectionString}`."); 20 | 21 | builder.UseSqlServer (connectionString); 22 | return new MyWalletContext (builder.Options); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/EntityFrameworkDataAccess/Entities/Account.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Infrastructure.EntityFrameworkDataAccess.Entities { 2 | using System; 3 | 4 | public class Account { 5 | public Guid Id { get; set; } 6 | public Guid CustomerId { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/EntityFrameworkDataAccess/Entities/Credit.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Infrastructure.EntityFrameworkDataAccess.Entities { 2 | using System; 3 | 4 | public class Credit { 5 | public Guid Id { get; set; } 6 | public Guid AccountId { get; set; } 7 | public decimal Amount { get; set; } 8 | public DateTime TransactionDate { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/EntityFrameworkDataAccess/Entities/Customer.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Infrastructure.EntityFrameworkDataAccess.Entities { 2 | using System; 3 | 4 | public class Customer { 5 | public Guid Id { get; set; } 6 | public string Name { get; set; } 7 | public string SSN { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/EntityFrameworkDataAccess/Entities/Debit.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Infrastructure.EntityFrameworkDataAccess.Entities { 2 | using System; 3 | 4 | public class Debit { 5 | public Guid Id { get; set; } 6 | public Guid AccountId { get; set; } 7 | public decimal Amount { get; set; } 8 | public DateTime TransactionDate { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/EntityFrameworkDataAccess/EntityFrameworkModule.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Infrastructure.EntityFrameworkDataAccess { 2 | using Autofac; 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | public class EntityFrameworkModule : Autofac.Module { 6 | public string ConnectionString { get; set; } 7 | 8 | protected override void Load (ContainerBuilder builder) { 9 | var optionsBuilder = new DbContextOptionsBuilder (); 10 | optionsBuilder.UseSqlServer (ConnectionString); 11 | optionsBuilder.EnableSensitiveDataLogging (true); 12 | 13 | builder.RegisterType () 14 | .WithParameter (new TypedParameter (typeof (DbContextOptions), optionsBuilder.Options)) 15 | .InstancePerLifetimeScope (); 16 | 17 | // 18 | // Register all Types in MongoDataAccess namespace 19 | builder.RegisterAssemblyTypes (typeof (InfrastructureException).Assembly) 20 | .Where (type => type.Namespace.Contains ("EntityFrameworkDataAccess")) 21 | .AsImplementedInterfaces () 22 | .InstancePerLifetimeScope (); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/EntityFrameworkDataAccess/FinanceContext.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Infrastructure.EntityFrameworkDataAccess { 2 | using Microsoft.EntityFrameworkCore; 3 | using Entities; 4 | public sealed class MyWalletContext : DbContext { 5 | public MyWalletContext (DbContextOptions options) : base (options) { 6 | 7 | } 8 | 9 | public DbSet Accounts { get; set; } 10 | public DbSet Customers { get; set; } 11 | public DbSet Credits { get; set; } 12 | public DbSet Debits { get; set; } 13 | 14 | protected override void OnModelCreating (ModelBuilder modelBuilder) { 15 | modelBuilder.Entity () 16 | .ToTable ("Account"); 17 | 18 | modelBuilder.Entity () 19 | .ToTable ("Customer"); 20 | 21 | modelBuilder.Entity () 22 | .ToTable ("Debit"); 23 | 24 | modelBuilder.Entity () 25 | .ToTable ("Credit"); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/EntityFrameworkDataAccess/Migrations/20181111112931_InitialCreate.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using MyWallet.Infrastructure.EntityFrameworkDataAccess; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Migrations; 8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 9 | 10 | namespace MyWallet.Infrastructure.EntityFrameworkDataAccess.Migrations 11 | { 12 | [DbContext(typeof(MyWalletContext))] 13 | [Migration("20181111112931_InitialCreate")] 14 | partial class InitialCreate 15 | { 16 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "2.1.4-rtm-31024") 21 | .HasAnnotation("Relational:MaxIdentifierLength", 128) 22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 23 | 24 | modelBuilder.Entity("MyWallet.Infrastructure.EntityFrameworkDataAccess.Entities.Account", b => 25 | { 26 | b.Property("Id") 27 | .ValueGeneratedOnAdd(); 28 | 29 | b.Property("CustomerId"); 30 | 31 | b.HasKey("Id"); 32 | 33 | b.ToTable("Account"); 34 | }); 35 | 36 | modelBuilder.Entity("MyWallet.Infrastructure.EntityFrameworkDataAccess.Entities.Credit", b => 37 | { 38 | b.Property("Id") 39 | .ValueGeneratedOnAdd(); 40 | 41 | b.Property("AccountId"); 42 | 43 | b.Property("Amount"); 44 | 45 | b.Property("TransactionDate"); 46 | 47 | b.HasKey("Id"); 48 | 49 | b.ToTable("Credit"); 50 | }); 51 | 52 | modelBuilder.Entity("MyWallet.Infrastructure.EntityFrameworkDataAccess.Entities.Customer", b => 53 | { 54 | b.Property("Id") 55 | .ValueGeneratedOnAdd(); 56 | 57 | b.Property("Name"); 58 | 59 | b.Property("SSN"); 60 | 61 | b.HasKey("Id"); 62 | 63 | b.ToTable("Customer"); 64 | }); 65 | 66 | modelBuilder.Entity("MyWallet.Infrastructure.EntityFrameworkDataAccess.Entities.Debit", b => 67 | { 68 | b.Property("Id") 69 | .ValueGeneratedOnAdd(); 70 | 71 | b.Property("AccountId"); 72 | 73 | b.Property("Amount"); 74 | 75 | b.Property("TransactionDate"); 76 | 77 | b.HasKey("Id"); 78 | 79 | b.ToTable("Debit"); 80 | }); 81 | #pragma warning restore 612, 618 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/EntityFrameworkDataAccess/Migrations/20181111112931_InitialCreate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | namespace MyWallet.Infrastructure.EntityFrameworkDataAccess.Migrations 5 | { 6 | public partial class InitialCreate : Migration 7 | { 8 | protected override void Up(MigrationBuilder migrationBuilder) 9 | { 10 | migrationBuilder.CreateTable( 11 | name: "Account", 12 | columns: table => new 13 | { 14 | Id = table.Column(nullable: false), 15 | CustomerId = table.Column(nullable: false) 16 | }, 17 | constraints: table => 18 | { 19 | table.PrimaryKey("PK_Account", x => x.Id); 20 | }); 21 | 22 | migrationBuilder.CreateTable( 23 | name: "Credit", 24 | columns: table => new 25 | { 26 | Id = table.Column(nullable: false), 27 | AccountId = table.Column(nullable: false), 28 | Amount = table.Column(nullable: false), 29 | TransactionDate = table.Column(nullable: false) 30 | }, 31 | constraints: table => 32 | { 33 | table.PrimaryKey("PK_Credit", x => x.Id); 34 | }); 35 | 36 | migrationBuilder.CreateTable( 37 | name: "Customer", 38 | columns: table => new 39 | { 40 | Id = table.Column(nullable: false), 41 | Name = table.Column(nullable: true), 42 | SSN = table.Column(nullable: true) 43 | }, 44 | constraints: table => 45 | { 46 | table.PrimaryKey("PK_Customer", x => x.Id); 47 | }); 48 | 49 | migrationBuilder.CreateTable( 50 | name: "Debit", 51 | columns: table => new 52 | { 53 | Id = table.Column(nullable: false), 54 | AccountId = table.Column(nullable: false), 55 | Amount = table.Column(nullable: false), 56 | TransactionDate = table.Column(nullable: false) 57 | }, 58 | constraints: table => 59 | { 60 | table.PrimaryKey("PK_Debit", x => x.Id); 61 | }); 62 | } 63 | 64 | protected override void Down(MigrationBuilder migrationBuilder) 65 | { 66 | migrationBuilder.DropTable( 67 | name: "Account"); 68 | 69 | migrationBuilder.DropTable( 70 | name: "Credit"); 71 | 72 | migrationBuilder.DropTable( 73 | name: "Customer"); 74 | 75 | migrationBuilder.DropTable( 76 | name: "Debit"); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/EntityFrameworkDataAccess/Migrations/FinanceContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using MyWallet.Infrastructure.EntityFrameworkDataAccess; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | 9 | namespace MyWallet.Infrastructure.EntityFrameworkDataAccess.Migrations 10 | { 11 | [DbContext(typeof(MyWalletContext))] 12 | partial class MyWalletContextModelSnapshot : ModelSnapshot 13 | { 14 | protected override void BuildModel(ModelBuilder modelBuilder) 15 | { 16 | #pragma warning disable 612, 618 17 | modelBuilder 18 | .HasAnnotation("ProductVersion", "2.1.4-rtm-31024") 19 | .HasAnnotation("Relational:MaxIdentifierLength", 128) 20 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 21 | 22 | modelBuilder.Entity("MyWallet.Infrastructure.EntityFrameworkDataAccess.Entities.Account", b => 23 | { 24 | b.Property("Id") 25 | .ValueGeneratedOnAdd(); 26 | 27 | b.Property("CustomerId"); 28 | 29 | b.HasKey("Id"); 30 | 31 | b.ToTable("Account"); 32 | }); 33 | 34 | modelBuilder.Entity("MyWallet.Infrastructure.EntityFrameworkDataAccess.Entities.Credit", b => 35 | { 36 | b.Property("Id") 37 | .ValueGeneratedOnAdd(); 38 | 39 | b.Property("AccountId"); 40 | 41 | b.Property("Amount"); 42 | 43 | b.Property("TransactionDate"); 44 | 45 | b.HasKey("Id"); 46 | 47 | b.ToTable("Credit"); 48 | }); 49 | 50 | modelBuilder.Entity("MyWallet.Infrastructure.EntityFrameworkDataAccess.Entities.Customer", b => 51 | { 52 | b.Property("Id") 53 | .ValueGeneratedOnAdd(); 54 | 55 | b.Property("Name"); 56 | 57 | b.Property("SSN"); 58 | 59 | b.HasKey("Id"); 60 | 61 | b.ToTable("Customer"); 62 | }); 63 | 64 | modelBuilder.Entity("MyWallet.Infrastructure.EntityFrameworkDataAccess.Entities.Debit", b => 65 | { 66 | b.Property("Id") 67 | .ValueGeneratedOnAdd(); 68 | 69 | b.Property("AccountId"); 70 | 71 | b.Property("Amount"); 72 | 73 | b.Property("TransactionDate"); 74 | 75 | b.HasKey("Id"); 76 | 77 | b.ToTable("Debit"); 78 | }); 79 | #pragma warning restore 612, 618 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/EntityFrameworkDataAccess/Repositories/AccountRepository.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Infrastructure.EntityFrameworkDataAccess { 2 | using System.Collections.Generic; 3 | using System.Data.SqlClient; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using System; 7 | using MyWallet.Application.Repositories; 8 | using MyWallet.Domain.Accounts; 9 | using Microsoft.EntityFrameworkCore; 10 | 11 | public class AccountRepository : IAccountReadOnlyRepository, IAccountWriteOnlyRepository { 12 | private readonly MyWalletContext _context; 13 | 14 | public AccountRepository (MyWalletContext context) { 15 | _context = context ?? 16 | throw new ArgumentNullException (nameof (context)); 17 | } 18 | 19 | public async Task Add (Account account, Credit credit) { 20 | Entities.Account accountEntity = new Entities.Account () { 21 | CustomerId = account.CustomerId, 22 | Id = account.Id 23 | }; 24 | 25 | Entities.Credit creditEntity = new Entities.Credit () { 26 | AccountId = credit.AccountId, 27 | Amount = credit.Amount, 28 | Id = credit.Id, 29 | TransactionDate = credit.TransactionDate 30 | }; 31 | 32 | await _context.Accounts.AddAsync (accountEntity); 33 | await _context.Credits.AddAsync (creditEntity); 34 | await _context.SaveChangesAsync (); 35 | } 36 | 37 | public async Task Delete (Account account) { 38 | string deleteSQL = 39 | @"DELETE FROM Credit WHERE AccountId = @Id; 40 | DELETE FROM Debit WHERE AccountId = @Id; 41 | DELETE FROM Account WHERE Id = @Id;"; 42 | 43 | var id = new SqlParameter ("@Id", account.Id); 44 | 45 | int affectedRows = await _context.Database.ExecuteSqlCommandAsync ( 46 | deleteSQL, id); 47 | } 48 | 49 | public async Task Get (Guid id) { 50 | Entities.Account account = await _context 51 | .Accounts 52 | .FindAsync (id); 53 | 54 | List credits = await _context 55 | .Credits 56 | .Where (e => e.AccountId == id) 57 | .ToListAsync (); 58 | 59 | List debits = await _context 60 | .Debits 61 | .Where (e => e.AccountId == id) 62 | .ToListAsync (); 63 | 64 | List transactions = new List (); 65 | 66 | foreach (Entities.Credit transactionData in credits) { 67 | Credit transaction = Credit.LoadFromDetails ( 68 | transactionData.Id, 69 | transactionData.AccountId, 70 | transactionData.Amount, 71 | transactionData.TransactionDate); 72 | 73 | transactions.Add (transaction); 74 | } 75 | 76 | foreach (Entities.Debit transactionData in debits) { 77 | Debit transaction = Debit.LoadFromDetails ( 78 | transactionData.Id, 79 | transactionData.AccountId, 80 | transactionData.Amount, 81 | transactionData.TransactionDate); 82 | 83 | transactions.Add (transaction); 84 | } 85 | 86 | var orderedTransactions = transactions.OrderBy (o => o.TransactionDate).ToList (); 87 | 88 | TransactionCollection transactionCollection = new TransactionCollection (); 89 | transactionCollection.Add (orderedTransactions); 90 | 91 | Account result = Account.LoadFromDetails ( 92 | account.Id, 93 | account.CustomerId, 94 | transactionCollection); 95 | 96 | return result; 97 | } 98 | 99 | public async Task Update (Account account, Credit credit) { 100 | Entities.Credit creditEntity = new Entities.Credit { 101 | AccountId = credit.AccountId, 102 | Amount = credit.Amount, 103 | Id = credit.Id, 104 | TransactionDate = credit.TransactionDate 105 | }; 106 | 107 | await _context.Credits.AddAsync (creditEntity); 108 | await _context.SaveChangesAsync (); 109 | } 110 | 111 | public async Task Update (Account account, Debit debit) { 112 | Entities.Debit debitEntity = new Entities.Debit { 113 | AccountId = debit.AccountId, 114 | Amount = debit.Amount, 115 | Id = debit.Id, 116 | TransactionDate = debit.TransactionDate 117 | }; 118 | 119 | await _context.Debits.AddAsync (debitEntity); 120 | await _context.SaveChangesAsync (); 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/EntityFrameworkDataAccess/Repositories/CustomerRepository.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Infrastructure.EntityFrameworkDataAccess { 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System; 6 | using MyWallet.Application.Repositories; 7 | using MyWallet.Domain.Customers; 8 | using Microsoft.EntityFrameworkCore; 9 | 10 | public class CustomerRepository : ICustomerReadOnlyRepository, ICustomerWriteOnlyRepository { 11 | private readonly MyWalletContext _context; 12 | 13 | public CustomerRepository (MyWalletContext context) { 14 | _context = context ?? 15 | throw new ArgumentNullException (nameof (context)); 16 | } 17 | 18 | public async Task Add (Customer customer) { 19 | Entities.Customer customerEntity = new Entities.Customer () { 20 | Id = customer.Id, 21 | Name = customer.Name, 22 | SSN = customer.SSN 23 | }; 24 | 25 | await _context.Customers.AddAsync (customerEntity); 26 | await _context.SaveChangesAsync (); 27 | } 28 | 29 | public async Task Get (Guid id) { 30 | Entities.Customer customer = await _context.Customers 31 | .FindAsync (id); 32 | 33 | List accounts = await _context.Accounts 34 | .Where (e => e.CustomerId == id) 35 | .Select (p => p.Id) 36 | .ToListAsync (); 37 | 38 | AccountCollection accountCollection = new AccountCollection (); 39 | foreach (var accountId in accounts) 40 | accountCollection.Add (accountId); 41 | 42 | return Customer.LoadFromDetails (customer.Id, customer.Name, customer.SSN, accountCollection); 43 | } 44 | 45 | public async Task Update (Customer customer) { 46 | Entities.Customer customerEntity = new Entities.Customer () { 47 | Id = customer.Id, 48 | Name = customer.Name, 49 | SSN = customer.SSN 50 | }; 51 | 52 | _context.Customers.Update (customerEntity); 53 | await _context.SaveChangesAsync (); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/InMemoryDataAccess/FinanceContext.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Infrastructure.InMemoryDataAccess { 2 | using System.Collections.ObjectModel; 3 | using MyWallet.Domain.Accounts; 4 | using MyWallet.Domain.Customers; 5 | 6 | public sealed class MyWalletContext { 7 | public Collection Customers { get; set; } 8 | public Collection Accounts { get; set; } 9 | 10 | public MyWalletContext () { 11 | Customers = new Collection (); 12 | Accounts = new Collection (); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/InMemoryDataAccess/InMemoryModule.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Infrastructure.InMemoryDataAccess { 2 | using Autofac; 3 | 4 | public class InMemoryModule : Autofac.Module { 5 | protected override void Load (ContainerBuilder builder) { 6 | builder.RegisterType () 7 | .As () 8 | .SingleInstance (); 9 | // 10 | // Register all Types in InMemoryDataAccess namespace 11 | builder.RegisterAssemblyTypes (typeof (InfrastructureException).Assembly) 12 | .Where (type => type.Namespace.Contains ("InMemoryDataAccess")) 13 | .AsImplementedInterfaces () 14 | .InstancePerLifetimeScope (); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/InMemoryDataAccess/Repositories/AccountRepository.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Infrastructure.InMemoryDataAccess.Repositories { 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using System; 5 | using MyWallet.Application.Repositories; 6 | using MyWallet.Domain.Accounts; 7 | 8 | public class AccountRepository : IAccountReadOnlyRepository, IAccountWriteOnlyRepository { 9 | private readonly MyWalletContext _context; 10 | 11 | public AccountRepository (MyWalletContext context) { 12 | _context = context; 13 | } 14 | 15 | public async Task Add (Account account, Credit credit) { 16 | _context.Accounts.Add (account); 17 | await Task.CompletedTask; 18 | } 19 | 20 | public async Task Delete (Account account) { 21 | Account accountOld = _context.Accounts 22 | .Where (e => e.Id == account.Id) 23 | .SingleOrDefault (); 24 | 25 | _context.Accounts.Remove (accountOld); 26 | 27 | await Task.CompletedTask; 28 | } 29 | 30 | public async Task Get (Guid id) { 31 | Account account = _context.Accounts 32 | .Where (e => e.Id == id) 33 | .SingleOrDefault (); 34 | 35 | return await Task.FromResult (account); 36 | } 37 | 38 | public async Task Update (Account account, Credit credit) { 39 | Account accountOld = _context.Accounts 40 | .Where (e => e.Id == account.Id) 41 | .SingleOrDefault (); 42 | 43 | accountOld = account; 44 | await Task.CompletedTask; 45 | } 46 | 47 | public async Task Update (Account account, Debit debit) { 48 | Account accountOld = _context.Accounts 49 | .Where (e => e.Id == account.Id) 50 | .SingleOrDefault (); 51 | 52 | accountOld = account; 53 | await Task.CompletedTask; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/InMemoryDataAccess/Repositories/CustomerRepository.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Infrastructure.InMemoryDataAccess.Repositories { 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using System; 5 | using MyWallet.Application.Repositories; 6 | using MyWallet.Domain.Customers; 7 | 8 | public class CustomerRepository : ICustomerReadOnlyRepository, ICustomerWriteOnlyRepository { 9 | private readonly MyWalletContext _context; 10 | 11 | public CustomerRepository (MyWalletContext context) { 12 | _context = context; 13 | } 14 | 15 | public async Task Add (Customer customer) { 16 | _context.Customers.Add (customer); 17 | await Task.CompletedTask; 18 | } 19 | 20 | public async Task Get (Guid id) { 21 | Customer customer = _context.Customers 22 | .Where (e => e.Id == id) 23 | .SingleOrDefault (); 24 | 25 | return await Task.FromResult (customer); 26 | } 27 | 28 | public async Task Update (Customer customer) { 29 | Customer customerOld = _context.Customers 30 | .Where (e => e.Id == customer.Id) 31 | .SingleOrDefault (); 32 | 33 | customerOld = customer; 34 | await Task.CompletedTask; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/InfrastructureException.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.Infrastructure { 2 | using System; 3 | public class InfrastructureException : Exception { 4 | internal InfrastructureException (string businessMessage) : base (businessMessage) { } 5 | } 6 | } -------------------------------------------------------------------------------- /source/MyWallet.Infrastructure/MyWallet.Infrastructure.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0; 5 | 6 | 7 | 8 | 1701;1702;1705;NU1701;CS1591 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /source/MyWallet.WebApi/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivanpaulovich/clean-architecture-webapi-ef-core/6d3ac936bb87a94cc926a9730e93082d2ba5acee/source/MyWallet.WebApi/.DS_Store -------------------------------------------------------------------------------- /source/MyWallet.WebApi/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp2.0/MyWallet.WebApi.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | "stopAtEntry": false, 17 | "internalConsoleOptions": "openOnSessionStart", 18 | "launchBrowser": { 19 | "enabled": true, 20 | "args": "${auto-detect-url}", 21 | "windows": { 22 | "command": "cmd.exe", 23 | "args": "/C start ${auto-detect-url}" 24 | }, 25 | "osx": { 26 | "command": "open" 27 | }, 28 | "linux": { 29 | "command": "xdg-open" 30 | } 31 | }, 32 | "env": { 33 | "ASPNETCORE_ENVIRONMENT": "Development" 34 | }, 35 | "sourceFileMap": { 36 | "/Views": "${workspaceFolder}/Views" 37 | } 38 | }, 39 | { 40 | "name": ".NET Core Attach", 41 | "type": "coreclr", 42 | "request": "attach", 43 | "processId": "${command:pickProcess}" 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/MyWallet.WebApi.csproj" 11 | ], 12 | "problemMatcher": "$msCompile" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/Filters/DomainExceptionFilter.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.Filters { 2 | using System.Net; 3 | using MyWallet.Domain; 4 | using Microsoft.AspNetCore.Mvc.Filters; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Newtonsoft.Json; 7 | 8 | public class DomainExceptionFilter : IExceptionFilter { 9 | public void OnException (ExceptionContext context) { 10 | DomainException domainException = context.Exception as DomainException; 11 | if (domainException != null) { 12 | string json = JsonConvert.SerializeObject (domainException.Message); 13 | 14 | context.Result = new BadRequestObjectResult (json); 15 | context.HttpContext.Response.StatusCode = (int) HttpStatusCode.BadRequest; 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/Filters/ValidateModelAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.Filters { 2 | using Microsoft.AspNetCore.Mvc.Filters; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | public class ValidateModelAttribute : ActionFilterAttribute { 6 | public override void OnActionExecuting (ActionExecutingContext context) { 7 | if (!context.ModelState.IsValid) { 8 | context.Result = new BadRequestObjectResult (context.ModelState); 9 | } 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/MyWallet.WebApi.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | full 8 | 9 | 10 | 11 | 1701;1702;1705;NU1701;CS1591 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | PreserveNewest 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /source/MyWallet.WebApi/Program.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi { 2 | using System.IO; 3 | using Autofac.Extensions.DependencyInjection; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.AspNetCore; 6 | using Microsoft.Extensions.Configuration; 7 | using Serilog.Events; 8 | using Serilog; 9 | 10 | public class Program { 11 | public static void Main (string[] args) { 12 | BuildWebHost (args).Run (); 13 | } 14 | 15 | public static IWebHost BuildWebHost (string[] args) { 16 | return WebHost.CreateDefaultBuilder (args) 17 | .UseStartup () 18 | .ConfigureAppConfiguration ((builderContext, config) => { 19 | IHostingEnvironment env = builderContext.HostingEnvironment; 20 | config.AddJsonFile ("appsettings.json"); 21 | config.AddJsonFile ("autofac.json"); 22 | config.AddJsonFile ($"autofac.{env.EnvironmentName}.json", optional: true); 23 | config.AddEnvironmentVariables (); 24 | }) 25 | .UseSerilog ((hostingContext, loggerConfiguration) => { 26 | loggerConfiguration.MinimumLevel.Debug () 27 | .MinimumLevel.Override ("Microsoft", LogEventLevel.Information) 28 | .Enrich.FromLogContext () 29 | .WriteTo.RollingFile (Path.Combine (hostingContext.HostingEnvironment.ContentRootPath, "logs/log-{Date}.log")); 30 | }) 31 | .ConfigureServices (services => services.AddAutofac ()) 32 | .Build (); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi { 2 | using System.IO; 3 | using System.Reflection; 4 | using Autofac.Configuration; 5 | using Autofac; 6 | using MyWallet.WebApi.Filters; 7 | using Microsoft.AspNetCore.Builder; 8 | using Microsoft.AspNetCore.Hosting; 9 | using Microsoft.Extensions.Configuration; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Swashbuckle.AspNetCore.Swagger; 12 | 13 | public class Startup { 14 | public Startup (IConfiguration configuration) { 15 | Configuration = configuration; 16 | } 17 | 18 | public IConfiguration Configuration { get; } 19 | 20 | public void ConfigureServices (IServiceCollection services) { 21 | services.AddCors (options => { 22 | options.AddPolicy ("CorsPolicy", 23 | builder => builder.AllowAnyOrigin () 24 | .AllowAnyMethod () 25 | .AllowAnyHeader () 26 | .AllowCredentials ()); 27 | }); 28 | 29 | services.AddMvc (options => { 30 | options.Filters.Add (typeof (DomainExceptionFilter)); 31 | options.Filters.Add (typeof (ValidateModelAttribute)); 32 | }); 33 | 34 | services.AddSwaggerGen (c => { 35 | c.SwaggerDoc ("v1", new Info { Title = "My API", Version = "v1" }); 36 | }); 37 | } 38 | 39 | public void ConfigureContainer (ContainerBuilder builder) { 40 | builder.RegisterModule (new ConfigurationModule (Configuration)); 41 | } 42 | 43 | public void Configure (IApplicationBuilder app, IHostingEnvironment env) { 44 | if (env.IsDevelopment ()) { 45 | app.UseDeveloperExceptionPage (); 46 | } 47 | 48 | app.UseCors ("CorsPolicy"); 49 | 50 | app.UseMvc (); 51 | 52 | app.UseSwagger (); 53 | 54 | app.UseSwaggerUI (c => { 55 | c.SwaggerEndpoint ("/swagger/v1/swagger.json", "My API V1"); 56 | c.RoutePrefix = string.Empty; 57 | }); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/AccountDetailsModel.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases { 2 | using System.Collections.Generic; 3 | using System; 4 | 5 | public sealed class AccountDetailsModel { 6 | public Guid AccountId { get; } 7 | public decimal CurrentBalance { get; } 8 | public List Transactions { get; } 9 | 10 | public AccountDetailsModel (Guid accountId, decimal currentBalance, List transactions) { 11 | AccountId = accountId; 12 | CurrentBalance = currentBalance; 13 | Transactions = transactions; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/CloseAccount/AccountsController.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases.CloseAccount { 2 | using System.Threading.Tasks; 3 | using System; 4 | using MyWallet.Application.UseCases.CloseAccount; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | [Route ("api/[controller]")] 8 | public class AccountsController : Controller { 9 | private readonly ICloseAccountUseCase _closeAccountUseCase; 10 | private readonly Presenter _presenter; 11 | 12 | public AccountsController ( 13 | ICloseAccountUseCase closeAccountUseCase, 14 | Presenter presenter) { 15 | _closeAccountUseCase = closeAccountUseCase; 16 | _presenter = presenter; 17 | } 18 | 19 | /// 20 | /// Close the account 21 | /// 22 | [HttpDelete ("{accountId}")] 23 | public async Task Close (Guid accountId) { 24 | Guid output = await _closeAccountUseCase.Execute (accountId); 25 | _presenter.Populate (output); 26 | return _presenter.ViewModel; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/CloseAccount/Presenter.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases.CloseAccount { 2 | using System; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | public sealed class Presenter { 6 | public IActionResult ViewModel { get; private set; } 7 | 8 | public void Populate (Guid output) { 9 | if (output == null) { 10 | ViewModel = new NoContentResult (); 11 | return; 12 | } 13 | 14 | ViewModel = new OkResult (); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/CustomerDetailsModel.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases { 2 | using System.Collections.Generic; 3 | using System; 4 | 5 | public sealed class CustomerDetailsModel { 6 | public Guid CustomerId { get; } 7 | public string Personnummer { get; } 8 | public string Name { get; } 9 | public List Accounts { get; } 10 | 11 | public CustomerDetailsModel (Guid customerId, string personnummer, string name, List accounts) { 12 | CustomerId = customerId; 13 | Personnummer = personnummer; 14 | Name = name; 15 | Accounts = accounts; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/Deposit/AccountsController.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases.Deposit { 2 | using System.Threading.Tasks; 3 | using MyWallet.Application.UseCases.Deposit; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | [Route ("api/[controller]")] 7 | public class AccountsController : Controller { 8 | private readonly IDepositUseCase _depositUseCase; 9 | private readonly Presenter _presenter; 10 | 11 | public AccountsController ( 12 | IDepositUseCase depositUseCase, 13 | Presenter presenter) { 14 | _depositUseCase = depositUseCase; 15 | _presenter = presenter; 16 | } 17 | 18 | /// 19 | /// Deposit to an account 20 | /// 21 | [HttpPatch ("Deposit")] 22 | public async Task Deposit ([FromBody] DepositRequest message) { 23 | var output = await _depositUseCase.Execute (message.AccountId, message.Amount); 24 | _presenter.Populate (output); 25 | return _presenter.ViewModel; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/Deposit/CurrentAccountBalanceModel.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases.Deposit { 2 | using System; 3 | 4 | public class CurrentAccountBalanceModel { 5 | public decimal Amount { get; } 6 | public string Description { get; } 7 | public DateTime TransactionDate { get; } 8 | public decimal UpdateBalance { get; } 9 | 10 | public CurrentAccountBalanceModel ( 11 | decimal amount, 12 | string description, 13 | DateTime transactionDate, 14 | decimal updatedBalance) { 15 | Amount = amount; 16 | Description = description; 17 | TransactionDate = transactionDate; 18 | UpdateBalance = updatedBalance; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/Deposit/DepositRequest.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases.Deposit { 2 | using System; 3 | public class DepositRequest { 4 | public Guid AccountId { get; set; } 5 | public decimal Amount { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/Deposit/Presenter.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases.Deposit { 2 | using MyWallet.Application.UseCases.Deposit; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | public class Presenter { 6 | public IActionResult ViewModel { get; private set; } 7 | 8 | public void Populate (DepositOutput output) { 9 | if (output == null) { 10 | ViewModel = new NoContentResult (); 11 | return; 12 | } 13 | 14 | ViewModel = new ObjectResult (new CurrentAccountBalanceModel ( 15 | output.Transaction.Amount, 16 | output.Transaction.Description, 17 | output.Transaction.TransactionDate, 18 | output.UpdatedBalance 19 | )); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/GetAccountDetails/AccountsController.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases.GetAccountDetails { 2 | using System.Threading.Tasks; 3 | using System; 4 | using MyWallet.Application.UseCases.GetAccountDetails; 5 | using MyWallet.Application.UseCases; 6 | using Microsoft.AspNetCore.Mvc; 7 | 8 | [Route ("api/[controller]")] 9 | public class AccountsController : Controller { 10 | private readonly IGetAccountDetailsUseCase _getAccountDetailsUseCase; 11 | private readonly Presenter _presenter; 12 | 13 | public AccountsController ( 14 | IGetAccountDetailsUseCase getAccountDetailsUseCase, 15 | Presenter presenter) { 16 | _getAccountDetailsUseCase = getAccountDetailsUseCase; 17 | _presenter = presenter; 18 | } 19 | 20 | /// 21 | /// Get an account balance 22 | /// 23 | [HttpGet ("{accountId}", Name = "GetAccount")] 24 | public async Task Get (Guid accountId) { 25 | AccountOutput output = await _getAccountDetailsUseCase.Execute (accountId); 26 | _presenter.Populate (output); 27 | return _presenter.ViewModel; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/GetAccountDetails/Presenter.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases.GetAccountDetails { 2 | using System.Collections.Generic; 3 | using MyWallet.Application.UseCases; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | public sealed class Presenter { 7 | public IActionResult ViewModel { get; private set; } 8 | 9 | public void Populate (AccountOutput output) { 10 | if (output == null) { 11 | ViewModel = new NoContentResult (); 12 | return; 13 | } 14 | 15 | List transactions = new List (); 16 | 17 | foreach (var item in output.Transactions) { 18 | var transaction = new TransactionModel ( 19 | item.Amount, 20 | item.Description, 21 | item.TransactionDate); 22 | 23 | transactions.Add (transaction); 24 | } 25 | 26 | ViewModel = new ObjectResult (new AccountDetailsModel ( 27 | output.AccountId, 28 | output.CurrentBalance, 29 | transactions)); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/GetCustomerDetails/CustomersController.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases.GetCustomerDetails { 2 | using System.Threading.Tasks; 3 | using System; 4 | using MyWallet.Application.UseCases.GetCustomerDetails; 5 | using MyWallet.Application.UseCases; 6 | using Microsoft.AspNetCore.Mvc; 7 | 8 | [Route ("api/[controller]")] 9 | public class CustomersController : Controller { 10 | private readonly IGetCustomerDetailsUseCase _getCustomerDetailsUseCase; 11 | private readonly Presenter _presenter; 12 | 13 | public CustomersController ( 14 | IGetCustomerDetailsUseCase getCustomerDetailsUseCase, 15 | Presenter presenter) { 16 | _getCustomerDetailsUseCase = getCustomerDetailsUseCase; 17 | _presenter = presenter; 18 | } 19 | 20 | /// 21 | /// Get a Customer details 22 | /// 23 | [HttpGet ("{customerId}", Name = "GetCustomer")] 24 | public async Task GetCustomer (Guid customerId) { 25 | CustomerOutput output = await _getCustomerDetailsUseCase.Execute (customerId); 26 | _presenter.Populate (output); 27 | return _presenter.ViewModel; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/GetCustomerDetails/Presenter.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases.GetCustomerDetails { 2 | using System.Collections.Generic; 3 | using MyWallet.Application.UseCases; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | public class Presenter { 7 | public IActionResult ViewModel { get; private set; } 8 | 9 | public void Populate (CustomerOutput output) { 10 | if (output == null) { 11 | ViewModel = new NoContentResult (); 12 | return; 13 | } 14 | 15 | List accounts = new List (); 16 | 17 | foreach (var account in output.Accounts) { 18 | List transactions = new List (); 19 | 20 | foreach (var item in account.Transactions) { 21 | var transaction = new TransactionModel ( 22 | item.Amount, 23 | item.Description, 24 | item.TransactionDate); 25 | 26 | transactions.Add (transaction); 27 | } 28 | 29 | accounts.Add (new AccountDetailsModel ( 30 | account.AccountId, 31 | account.CurrentBalance, 32 | transactions)); 33 | } 34 | 35 | CustomerDetailsModel model = new CustomerDetailsModel ( 36 | output.CustomerId, 37 | output.Personnummer, 38 | output.Name, 39 | accounts 40 | ); 41 | 42 | ViewModel = new ObjectResult (model); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/Register/CustomerModel.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases.Register { 2 | using System.Collections.Generic; 3 | using System; 4 | 5 | public class CustomerModel { 6 | public Guid CustomerId { get; } 7 | public string Personnummer { get; } 8 | public string Name { get; } 9 | public List Accounts { get; set; } 10 | 11 | public CustomerModel ( 12 | Guid customerId, 13 | string perssonnummer, 14 | string name, 15 | List accounts) { 16 | CustomerId = customerId; 17 | Personnummer = perssonnummer; 18 | Name = name; 19 | Accounts = accounts; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/Register/CustomersController.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases.Register { 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using MyWallet.Application.UseCases.Register; 5 | using MyWallet.Application.UseCases; 6 | using Microsoft.AspNetCore.Mvc; 7 | 8 | [Route ("api/[controller]")] 9 | public class CustomersController : Controller { 10 | private readonly IRegisterUseCase _registerUseCase; 11 | private readonly Presenter _presenter; 12 | 13 | public CustomersController ( 14 | IRegisterUseCase registerUseCase, 15 | Presenter presenter) { 16 | _registerUseCase = registerUseCase; 17 | _presenter = presenter; 18 | } 19 | 20 | /// 21 | /// Register a new Customer 22 | /// 23 | [HttpPost] 24 | public async Task Post ([FromBody] RegisterRequest request) { 25 | RegisterOutput output = await _registerUseCase.Execute ( 26 | request.Personnummer, 27 | request.Name, 28 | request.InitialAmount); 29 | 30 | _presenter.Populate (output); 31 | return _presenter.ViewModel; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/Register/Presenter.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases.Register { 2 | using System.Collections.Generic; 3 | using MyWallet.Application.UseCases.Register; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | public class Presenter { 7 | public IActionResult ViewModel { get; private set; } 8 | 9 | public void Populate (RegisterOutput response) { 10 | if (response == null) { 11 | ViewModel = new NoContentResult (); 12 | return; 13 | } 14 | 15 | List transactions = new List (); 16 | 17 | foreach (var item in response.Account.Transactions) { 18 | var transaction = new TransactionModel ( 19 | item.Amount, 20 | item.Description, 21 | item.TransactionDate); 22 | 23 | transactions.Add (transaction); 24 | } 25 | 26 | AccountDetailsModel account = new AccountDetailsModel ( 27 | response.Account.AccountId, 28 | response.Account.CurrentBalance, 29 | transactions); 30 | 31 | List accounts = new List (); 32 | accounts.Add (account); 33 | 34 | CustomerModel model = new CustomerModel ( 35 | response.Customer.CustomerId, 36 | response.Customer.Personnummer, 37 | response.Customer.Name, 38 | accounts 39 | ); 40 | 41 | ViewModel = new CreatedAtRouteResult ("GetCustomer", new { customerId = model.CustomerId }, model); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/Register/RegisterRequest.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases.Register { 2 | public class RegisterRequest { 3 | public string Personnummer { get; set; } 4 | public string Name { get; set; } 5 | public decimal InitialAmount { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/TransactionModel.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases { 2 | using System; 3 | 4 | public sealed class TransactionModel { 5 | public decimal Amount { get; } 6 | public string Description { get; } 7 | public DateTime TransactionDate { get; } 8 | public TransactionModel (decimal amount, string description, DateTime transactionDate) { 9 | Amount = amount; 10 | Description = description; 11 | TransactionDate = transactionDate; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/Withdraw/AccountsController.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases.Withdraw { 2 | using System.Threading.Tasks; 3 | using MyWallet.Application.UseCases.Withdraw; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | [Route ("api/[controller]")] 7 | public class AccountsController : Controller { 8 | private readonly IWithdrawUseCase _withdrawUseCase; 9 | private readonly Presenter _presenter; 10 | 11 | public AccountsController ( 12 | IWithdrawUseCase withdrawUseCase, 13 | Presenter presenter) { 14 | _withdrawUseCase = withdrawUseCase; 15 | _presenter = presenter; 16 | } 17 | 18 | /// 19 | /// Withdraw from an account 20 | /// 21 | [HttpPatch ("Withdraw")] 22 | public async Task Withdraw ([FromBody] WithdrawRequest request) { 23 | WithdrawOutput output = await _withdrawUseCase.Execute (request.AccountId, request.Amount); 24 | _presenter.Populate (output); 25 | return _presenter.ViewModel; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/Withdraw/CurrentBalanceModel.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases.Withdraw { 2 | using System; 3 | 4 | public class CurrentBalanceModel { 5 | public decimal Amount { get; } 6 | public string Description { get; } 7 | public DateTime TransactionDate { get; } 8 | public decimal UpdateBalance { get; } 9 | 10 | public CurrentBalanceModel (decimal amount, string description, DateTime transactionDate, decimal updatedBalance) { 11 | Amount = amount; 12 | Description = description; 13 | TransactionDate = transactionDate; 14 | UpdateBalance = updatedBalance; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/Withdraw/Presenter.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases.Withdraw { 2 | using MyWallet.Application.UseCases.Withdraw; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | public class Presenter { 6 | public IActionResult ViewModel { get; private set; } 7 | 8 | public void Populate (WithdrawOutput output) { 9 | if (output == null) { 10 | ViewModel = new NoContentResult (); 11 | return; 12 | } 13 | 14 | ViewModel = new ObjectResult (new { 15 | Amount = output.Transaction.Amount, 16 | Description = output.Transaction.Description, 17 | TransactionDate = output.Transaction.TransactionDate, 18 | UpdatedBalance = output.UpdatedBalance, 19 | }); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/UseCases/Withdraw/WithdrawRequest.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi.UseCases.Withdraw { 2 | using System; 3 | public class WithdrawRequest { 4 | public Guid AccountId { get; set; } 5 | public decimal Amount { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/WebApiModule.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.WebApi { 2 | using Autofac; 3 | 4 | public class WebApiModule : Autofac.Module { 5 | protected override void Load (ContainerBuilder builder) { 6 | // 7 | // Register all Types in MyWallet.WebApi 8 | builder.RegisterAssemblyTypes (typeof (Startup).Assembly) 9 | .AsSelf () 10 | .InstancePerLifetimeScope (); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "Debug": { 5 | "LogLevel": { 6 | "Default": "Warning" 7 | } 8 | }, 9 | "Console": { 10 | "LogLevel": { 11 | "Default": "Warning" 12 | } 13 | } 14 | }, 15 | "App": { 16 | "Title": "MyWallet: Clean Architecture", 17 | "Description": "Inspired by Uncle Bob, Vaughn Vernon and Gary Hall books", 18 | "Version": "v1", 19 | "TermsOfService": "TermsOfService" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /source/MyWallet.WebApi/autofac.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultAssembly": "MyWallet.Infrastructure", 3 | "modules": [ 4 | { 5 | "type": "MyWallet.WebApi.WebApiModule,MyWallet.WebApi", 6 | "properties": { 7 | } 8 | }, 9 | { 10 | "type": "MyWallet.Infrastructure.ApplicationModule", 11 | "properties": { 12 | } 13 | }, 14 | { 15 | "type": "MyWallet.Infrastructure.InMemoryDataAccess.InMemoryModule" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /source/MyWallet.WebApi/autofac.production.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultAssembly": "MyWallet.Infrastructure", 3 | "modules": [ 4 | { 5 | "type": "MyWallet.WebApi.WebApiModule,MyWallet.WebApi", 6 | "properties": { 7 | } 8 | }, 9 | { 10 | "type": "MyWallet.Infrastructure.ApplicationModule", 11 | "properties": { 12 | } 13 | }, 14 | { 15 | "type": "MyWallet.Infrastructure.EntityFrameworkDataAccess.EntityFrameworkModule", 16 | "properties": { 17 | "ConnectionString": "Server=localhost;Database=MyWalletDB;User Id=sa;Password=;" 18 | } 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /source/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | webapi: 4 | image: ivanpaulovich/clean-architecture-webapi-ef-core 5 | ports: 6 | - 5500:80 7 | environment: 8 | - ASPNETCORE_ENVIRONMENT=Development -------------------------------------------------------------------------------- /tests/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivanpaulovich/clean-architecture-webapi-ef-core/6d3ac936bb87a94cc926a9730e93082d2ba5acee/tests/.DS_Store -------------------------------------------------------------------------------- /tests/MyWallet.Domain.Tests/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivanpaulovich/clean-architecture-webapi-ef-core/6d3ac936bb87a94cc926a9730e93082d2ba5acee/tests/MyWallet.Domain.Tests/.DS_Store -------------------------------------------------------------------------------- /tests/MyWallet.Domain.Tests/AccountTests.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.DomainTests 2 | { 3 | using Xunit; 4 | using MyWallet.Domain.ValueObjects; 5 | using MyWallet.Domain.Accounts; 6 | using System; 7 | using Moq; 8 | 9 | public class AccountTests 10 | { 11 | [Theory] 12 | [InlineData(100)] 13 | [InlineData(0)] 14 | [InlineData(400)] 15 | public void Deposit_Should_Change_Balance_The_Same_Amount(decimal amountToDeposit) 16 | { 17 | // 18 | // Arrange 19 | Guid customerId = Guid.NewGuid(); 20 | Account sut = new Account(customerId); 21 | Amount amount = new Amount(amountToDeposit); 22 | 23 | // 24 | // Act 25 | sut.Deposit(amount); 26 | 27 | // 28 | // Assert 29 | Amount balance = sut.GetCurrentBalance(); 30 | 31 | Assert.Equal(customerId, sut.CustomerId); 32 | Assert.Equal(amountToDeposit, (decimal)balance); 33 | } 34 | 35 | [Fact] 36 | public void Deposit_Should_Change_Balance_When_Account_Is_New() 37 | { 38 | // 39 | // Arrange 40 | Guid expectedCustomerId = Guid.Parse("ac608347-74ac-4607-abc2-7b95cdc8a122"); 41 | Amount expectedAmount = new Amount(400m); 42 | 43 | // 44 | // Act 45 | Account sut = new Account(expectedCustomerId); 46 | sut.Deposit(expectedAmount); 47 | Amount balance = sut.GetCurrentBalance(); 48 | 49 | // 50 | // Assert 51 | Assert.Equal(expectedCustomerId, sut.CustomerId); 52 | Assert.Equal(expectedAmount, balance); 53 | Assert.Single(sut.Transactions.ToReadOnlyCollection()); 54 | } 55 | 56 | [Fact] 57 | public void Deposit_Should_Change_Balance_Equivalent_Amount() 58 | { 59 | // 60 | // Arrange 61 | Guid expectedCustomerId = Guid.Parse("ac608347-74ac-4607-abc2-7b95cdc8a122"); 62 | Amount expectedAmount = new Amount(400m); 63 | 64 | // 65 | // Act 66 | Account sut = new Account(expectedCustomerId); 67 | sut.Deposit(expectedAmount); 68 | Amount balance = sut.GetCurrentBalance(); 69 | 70 | // 71 | // Assert 72 | Assert.Equal(expectedAmount, balance); 73 | } 74 | 75 | [Fact] 76 | public void Deposit_Should_Add_Single_Transaction() 77 | { 78 | // 79 | // Arrange 80 | Guid expectedCustomerId = Guid.Parse("ac608347-74ac-4607-abc2-7b95cdc8a122"); 81 | Amount expectedAmount = new Amount(400m); 82 | 83 | // 84 | // Act 85 | Account sut = new Account(expectedCustomerId); 86 | sut.Deposit(expectedAmount); 87 | 88 | // 89 | // Assert 90 | Assert.Single(sut.Transactions.ToReadOnlyCollection()); 91 | } 92 | 93 | [Fact] 94 | public void NewAccount_Should_Return_The_Correct_CustomerId() 95 | { 96 | // 97 | // Arrange 98 | Guid expectedCustomerId = Guid.Parse("ac608347-74ac-4607-abc2-7b95cdc8a122"); 99 | Amount expectedAmount = new Amount(400m); 100 | 101 | // 102 | // Act 103 | Account sut = new Account(expectedCustomerId); 104 | 105 | // 106 | // Assert 107 | Assert.Equal(expectedCustomerId, sut.CustomerId); 108 | } 109 | 110 | [Fact] 111 | public void New_Account_With_1000_Balance_Should_Have_900_Credit_After_Withdraw() 112 | { 113 | // 114 | // Arrange 115 | Account sut = new Account(Guid.NewGuid()); 116 | sut.Deposit(1000.0m); 117 | 118 | // 119 | // Act 120 | sut.Withdraw(100); 121 | 122 | // 123 | // Assert 124 | Assert.Equal(900, sut.GetCurrentBalance()); 125 | } 126 | 127 | [Fact] 128 | public void New_Account_Should_Allow_Closing() 129 | { 130 | // 131 | // Arrange 132 | Account sut = new Account(Guid.NewGuid()); 133 | 134 | // 135 | // Act 136 | sut.Close(); 137 | 138 | // 139 | // Assert 140 | Assert.True(true); 141 | } 142 | 143 | [Fact] 144 | public void Account_With_Funds_Should_Not_Allow_Closing() 145 | { 146 | // 147 | // Arrange 148 | Account sut = new Account(Guid.NewGuid()); 149 | sut.Deposit(100); 150 | 151 | // 152 | // Act and Assert 153 | Assert.Throws( 154 | () => sut.Close()); 155 | } 156 | 157 | 158 | [Fact] 159 | public void Account_With_200_Balance_Should_Not_Allow_50000_Withdraw() 160 | { 161 | // 162 | // Arrange 163 | Account sut = new Account(Guid.NewGuid()); 164 | sut.Deposit(200); 165 | 166 | // 167 | // Act and Assert 168 | Assert.Throws( 169 | () => sut.Withdraw(5000)); 170 | } 171 | 172 | [Fact] 173 | public void Account_With_Three_Transactions_Should_Be_Consistent() 174 | { 175 | // 176 | // Arrange 177 | Account sut = new Account(Guid.NewGuid()); 178 | sut.Deposit(200); 179 | sut.Withdraw(100); 180 | sut.Deposit(50); 181 | 182 | // 183 | // Act and Assert 184 | 185 | var transactions = sut.Transactions; 186 | 187 | Assert.Equal(3, transactions.ToReadOnlyCollection().Count); 188 | } 189 | 190 | [Fact] 191 | public void Account_Should_Be_Loaded() 192 | { 193 | // 194 | // Arrange 195 | TransactionCollection transactions = new TransactionCollection(); 196 | transactions.Add(new Debit(Guid.Empty, 100)); 197 | 198 | // 199 | // Act 200 | Account account = Account.LoadFromDetails( 201 | Guid.Empty, 202 | Guid.Empty, 203 | transactions); 204 | 205 | // 206 | // Assert 207 | Assert.Single(account.Transactions.ToReadOnlyCollection()); 208 | Assert.Equal(Guid.Empty, account.Id); 209 | Assert.Equal(Guid.Empty, account.CustomerId); 210 | } 211 | 212 | [Fact] 213 | public void Deposit_Should_Throw_Exception_When_NegativeAmount() 214 | { 215 | // 216 | // Arrange and Act 217 | Account sut = new Account(Guid.NewGuid()); 218 | Exception ex = Record.Exception(() => sut.Deposit(-200)); 219 | 220 | // 221 | // Assert 222 | Assert.NotNull(ex); 223 | } 224 | 225 | [Fact] 226 | public void Withdraw_Should_Throw_Exception_When_NegativeAmount() 227 | { 228 | // 229 | // Arrange and Act 230 | Account sut = new Account(Guid.NewGuid()); 231 | Exception ex = Record.Exception(() => sut.Withdraw(-200)); 232 | 233 | // 234 | // Assert 235 | Assert.NotNull(ex); 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /tests/MyWallet.Domain.Tests/AmountTests.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.DomainTests 2 | { 3 | using MyWallet.Domain.ValueObjects; 4 | using System; 5 | using Xunit; 6 | 7 | public class AmountTests 8 | { 9 | [Theory] 10 | [InlineData(0)] 11 | [InlineData(100)] 12 | public void Create_Amount(decimal value) 13 | { 14 | // 15 | // Arrange 16 | decimal positiveAmount = value; 17 | 18 | // 19 | // Act 20 | Amount amount = new Amount(positiveAmount); 21 | 22 | // 23 | // Assert 24 | Assert.Equal(positiveAmount, amount); 25 | } 26 | 27 | [Fact] 28 | public void Amount_With_100_Minus_70_Should_Be_30() 29 | { 30 | // 31 | // Arrange 32 | Amount hundred = new Amount(100); 33 | Amount seventy = new Amount(70); 34 | 35 | // 36 | // Act 37 | Amount amount = hundred - seventy; 38 | 39 | // 40 | // Assert 41 | Assert.Equal(30, amount); 42 | } 43 | 44 | [Fact] 45 | public void Amount_With_100_Larger_Than_70() 46 | { 47 | // 48 | // Arrange 49 | Amount hundred = new Amount(100); 50 | Amount seventy = new Amount(70); 51 | 52 | // 53 | // Act & Assert 54 | Assert.True(hundred > seventy); 55 | } 56 | 57 | [Fact] 58 | public void Amount_With_30_Less_Than_Equal_30() 59 | { 60 | // 61 | // Arrange 62 | Amount thirty = new Amount(30); 63 | Amount seventy = new Amount(70); 64 | 65 | // 66 | // Act & Assert 67 | Assert.True(thirty <= seventy); 68 | } 69 | 70 | [Fact] 71 | public void Amount_With_10_Larger_Than_Equal_10() 72 | { 73 | // 74 | // Arrange 75 | Amount thirty = new Amount(10); 76 | Amount seventy = new Amount(10); 77 | 78 | // 79 | // Act & Assert 80 | Assert.True(thirty >= seventy); 81 | } 82 | 83 | [Fact] 84 | public void Amount_With_Negative_Throws_Exception() 85 | { 86 | // 87 | // Arrange Act 88 | Exception ex = Record.Exception(() => new Amount(-10)); 89 | 90 | Assert.NotNull(ex); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /tests/MyWallet.Domain.Tests/CreditTests.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.DomainTests 2 | { 3 | using Xunit; 4 | using MyWallet.Domain.Accounts; 5 | using System; 6 | 7 | public class CreditTests 8 | { 9 | [Fact] 10 | public void Credit_Should_Be_Loaded() 11 | { 12 | Credit credit = Credit.LoadFromDetails( 13 | Guid.Empty, 14 | Guid.Empty, 15 | 100, 16 | DateTime.Today); 17 | 18 | Assert.Equal(Guid.Empty, credit.Id); 19 | Assert.Equal(Guid.Empty, credit.AccountId); 20 | Assert.Equal(100, credit.Amount); 21 | Assert.Equal(DateTime.Today, credit.TransactionDate); 22 | Assert.Equal("Credit", credit.Description); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/MyWallet.Domain.Tests/CustomerTests.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.DomainTests 2 | { 3 | using Xunit; 4 | using MyWallet.Domain.Customers; 5 | using MyWallet.Domain.Accounts; 6 | using System; 7 | 8 | public class CustomerTests 9 | { 10 | [Fact] 11 | public void Customer_Should_Be_Registered_With_1_Account() 12 | { 13 | // 14 | // Arrange 15 | Customer sut = new Customer( 16 | "741214-3054", 17 | "Sammy Fredriksson"); 18 | 19 | var account = new Account(sut.Id); 20 | 21 | // 22 | // Act 23 | sut.Register(account.Id); 24 | 25 | // 26 | // Assert 27 | Assert.Single(sut.Accounts.ToReadOnlyCollection()); 28 | } 29 | 30 | [Fact] 31 | public void Customer_Should_Be_Loaded() 32 | { 33 | // 34 | // Arrange 35 | AccountCollection accounts = new AccountCollection(); 36 | accounts.Add(Guid.NewGuid()); 37 | 38 | Guid customerId = Guid.NewGuid(); 39 | 40 | Customer customer = Customer.LoadFromDetails( 41 | customerId, 42 | "Sammy Fredriksson", 43 | "741214-3054", 44 | accounts); 45 | 46 | Assert.Equal(customerId, customer.Id); 47 | Assert.Equal("Sammy Fredriksson", customer.Name); 48 | Assert.Equal("741214-3054", customer.SSN); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/MyWallet.Domain.Tests/DebitTests.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.DomainTests 2 | { 3 | using Xunit; 4 | using MyWallet.Domain.Accounts; 5 | using System; 6 | 7 | public class DebitTests 8 | { 9 | [Fact] 10 | public void Debit_Should_Be_Loaded() 11 | { 12 | Debit debit = Debit.LoadFromDetails( 13 | Guid.Empty, 14 | Guid.Empty, 15 | 100, 16 | DateTime.Today); 17 | 18 | Assert.Equal(Guid.Empty, debit.Id); 19 | Assert.Equal(Guid.Empty, debit.AccountId); 20 | Assert.Equal(100, debit.Amount); 21 | Assert.Equal(DateTime.Today, debit.TransactionDate); 22 | Assert.Equal("Debit", debit.Description); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/MyWallet.Domain.Tests/MyWallet.Domain.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/MyWallet.Domain.Tests/NameTests.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.DomainTests 2 | { 3 | using MyWallet.Domain.ValueObjects; 4 | using Xunit; 5 | 6 | public class NameTests 7 | { 8 | [Fact] 9 | public void Empty_Name_Should_Be_Created() 10 | { 11 | // 12 | // Arrange 13 | string empty = string.Empty; 14 | 15 | // 16 | // Act and Assert 17 | Assert.Throws( 18 | () => new Name(empty)); 19 | } 20 | 21 | [Fact] 22 | public void Full_Name_Shoud_Be_Created() 23 | { 24 | // 25 | // Arrange 26 | string valid = "Ivan Paulovich"; 27 | 28 | // 29 | // Act 30 | Name name = new Name(valid); 31 | 32 | // 33 | // Assert 34 | Assert.Equal(new Name(valid), name); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/MyWallet.Domain.Tests/SSNTests.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.DomainTests 2 | { 3 | using MyWallet.Domain.ValueObjects; 4 | using Xunit; 5 | 6 | public class SSNTests 7 | { 8 | [Fact] 9 | public void Empty_SSN_Should_Not_Be_Created() 10 | { 11 | // 12 | // Arrange 13 | string empty = string.Empty; 14 | 15 | // 16 | // Act and Assert 17 | Assert.Throws( 18 | () => new Personnummer(empty)); 19 | } 20 | 21 | [Fact] 22 | public void Valid_SSN_Should_Be_Created() 23 | { 24 | // 25 | // Arrange 26 | string valid = "08724050601"; 27 | 28 | // 29 | // Act 30 | Personnummer SSN = new Personnummer(valid); 31 | 32 | // Assert 33 | Assert.Equal(valid, SSN); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/MyWallet.Domain.Tests/TransactionCollectionTests.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.DomainTests 2 | { 3 | using Xunit; 4 | using MyWallet.Domain.Accounts; 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | public class TransactionCollectionTests 9 | { 10 | [Fact] 11 | public void Multiple_Transactions_Should_Be_Added() 12 | { 13 | TransactionCollection transactionCollection = new TransactionCollection(); 14 | transactionCollection.Add(new List() 15 | { 16 | new Credit(Guid.Empty, 100), 17 | new Debit(Guid.Empty, 30) 18 | }); 19 | 20 | Assert.Equal(2, transactionCollection.ToReadOnlyCollection().Count); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/MyWallet.UseCases.Tests/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivanpaulovich/clean-architecture-webapi-ef-core/6d3ac936bb87a94cc926a9730e93082d2ba5acee/tests/MyWallet.UseCases.Tests/.DS_Store -------------------------------------------------------------------------------- /tests/MyWallet.UseCases.Tests/AccountTests.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.UseCaseTests 2 | { 3 | using Xunit; 4 | using System; 5 | using MyWallet.Domain.Accounts; 6 | using MyWallet.Application.Repositories; 7 | using MyWallet.Application.UseCases.Register; 8 | using Moq; 9 | using MyWallet.Application.UseCases.Deposit; 10 | using MyWallet.Application.UseCases.Withdraw; 11 | using System.Threading.Tasks; 12 | 13 | public class AccountTests 14 | { 15 | [Theory] 16 | [InlineData("c725315a-1de6-4bf7-aecf-3af8f0083681", 100)] 17 | public async void Deposit_Valid_Amount(string accountId, decimal amount) 18 | { 19 | var mockAccountReadOnlyRepository = new Mock(); 20 | var mockAccountWriteOnlyRepository = new Mock(); 21 | 22 | Account account = new Account(Guid.Parse(accountId)); 23 | 24 | mockAccountReadOnlyRepository.SetupSequence(e => e.Get(It.IsAny())) 25 | .ReturnsAsync(account); 26 | 27 | DepositUseCase sut = new DepositUseCase( 28 | mockAccountReadOnlyRepository.Object, 29 | mockAccountWriteOnlyRepository.Object 30 | ); 31 | 32 | DepositOutput output = await sut.Execute( 33 | Guid.Parse(accountId), 34 | amount); 35 | 36 | Assert.Equal(100, output.UpdatedBalance); 37 | } 38 | 39 | [Theory] 40 | [InlineData("c725315a-1de6-4bf7-aecf-3af8f0083681", 100)] 41 | public async void Withdraw_Valid_Amount(string accountId, decimal amount) 42 | { 43 | var mockAccountReadOnlyRepository = new Mock(); 44 | var mockAccountWriteOnlyRepository = new Mock(); 45 | TransactionCollection transactions = new TransactionCollection(); 46 | transactions.Add(new Credit(Guid.Empty, 4000)); 47 | 48 | Account account = Account.LoadFromDetails(Guid.Parse(accountId), Guid.Empty, transactions); 49 | 50 | mockAccountReadOnlyRepository.SetupSequence(e => e.Get(It.IsAny())) 51 | .ReturnsAsync(account); 52 | 53 | WithdrawUseCase sut = new WithdrawUseCase( 54 | mockAccountReadOnlyRepository.Object, 55 | mockAccountWriteOnlyRepository.Object 56 | ); 57 | 58 | WithdrawOutput output = await sut.Execute( 59 | Guid.Parse(accountId), 60 | amount); 61 | 62 | Assert.Equal(3900, output.UpdatedBalance); 63 | } 64 | 65 | [Theory] 66 | [InlineData(100)] 67 | public void Account_With_Credits_Should_Not_Allow_Close(decimal amount) 68 | { 69 | Account account = new Account(Guid.NewGuid()); 70 | account.Deposit(amount); 71 | 72 | Assert.Throws( 73 | () => account.Close()); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/MyWallet.UseCases.Tests/CustomerTests.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.UseCaseTests 2 | { 3 | using Xunit; 4 | using MyWallet.Application.UseCases.Register; 5 | using MyWallet.Application.Repositories; 6 | using Moq; 7 | 8 | public class CustomerTests 9 | { 10 | [Theory] 11 | [InlineData(300)] 12 | [InlineData(100)] 13 | [InlineData(500)] 14 | [InlineData(3300)] 15 | public async void Register_Valid_User_Account(decimal amount) 16 | { 17 | string personnummer = "8608178888"; 18 | string name = "Ivan Paulovich"; 19 | 20 | var mockCustomerWriteOnlyRepository = new Mock(); 21 | var mockAccountWriteOnlyRepository = new Mock(); 22 | 23 | RegisterUseCase sut = new RegisterUseCase( 24 | mockCustomerWriteOnlyRepository.Object, 25 | mockAccountWriteOnlyRepository.Object 26 | ); 27 | 28 | RegisterOutput output = await sut.Execute( 29 | personnummer, 30 | name, 31 | amount); 32 | 33 | Assert.Equal(amount, output.Account.CurrentBalance); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/MyWallet.UseCases.Tests/MyWallet.UseCases.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/MyWallet.WebApi.Tests/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivanpaulovich/clean-architecture-webapi-ef-core/6d3ac936bb87a94cc926a9730e93082d2ba5acee/tests/MyWallet.WebApi.Tests/.DS_Store -------------------------------------------------------------------------------- /tests/MyWallet.WebApi.Tests/CustomerRegistration.cs: -------------------------------------------------------------------------------- 1 | namespace MyWallet.IntegrationTests 2 | { 3 | using Microsoft.AspNetCore.Hosting; 4 | using Autofac.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Configuration; 6 | using MyWallet.WebApi; 7 | using Microsoft.AspNetCore.TestHost; 8 | using Xunit; 9 | using System.Threading.Tasks; 10 | using System.Net.Http; 11 | using Newtonsoft.Json; 12 | using System.Text; 13 | using Newtonsoft.Json.Linq; 14 | using System; 15 | 16 | public class CustomerRegistration 17 | { 18 | private readonly TestServer server; 19 | private readonly HttpClient client; 20 | 21 | public CustomerRegistration() 22 | { 23 | var webHostBuilder = new WebHostBuilder() 24 | .UseStartup() 25 | .ConfigureAppConfiguration((builderContext, config) => 26 | { 27 | IHostingEnvironment env = builderContext.HostingEnvironment; 28 | config.AddJsonFile("autofac.json") 29 | .AddEnvironmentVariables(); 30 | }) 31 | .ConfigureServices(services => services.AddAutofac()); 32 | 33 | server = new TestServer(webHostBuilder); 34 | client = server.CreateClient(); 35 | } 36 | 37 | [Fact] 38 | public async Task Register_Deposit_Withdraw_Close() 39 | { 40 | Tuple customerId_accountId = await Register(100); 41 | await GetCustomer(customerId_accountId.Item1); 42 | await GetAccount(customerId_accountId.Item2); 43 | await Withdraw(customerId_accountId.Item2, 100); 44 | await GetCustomer(customerId_accountId.Item1); 45 | await Deposit(customerId_accountId.Item2, 500); 46 | await Deposit(customerId_accountId.Item2, 400); 47 | await GetCustomer(customerId_accountId.Item1); 48 | await Withdraw(customerId_accountId.Item2, 400); 49 | await Withdraw(customerId_accountId.Item2, 500); 50 | await Close(customerId_accountId.Item2); 51 | } 52 | 53 | private async Task GetCustomer(string customerId) 54 | { 55 | string result = await client.GetStringAsync("/api/Customers/" + customerId); 56 | } 57 | 58 | private async Task GetAccount(string accountId) 59 | { 60 | string result = await client.GetStringAsync("/api/Accounts/" + accountId); 61 | } 62 | 63 | private async Task> Register(double initialAmount) 64 | { 65 | var register = new 66 | { 67 | pin = "08724050601", 68 | name = "Ivan Paulovich", 69 | initialAmount = initialAmount 70 | }; 71 | 72 | string registerData = JsonConvert.SerializeObject(register); 73 | StringContent content = new StringContent(registerData, Encoding.UTF8, "application/json"); 74 | 75 | var response = await client.PostAsync("api/Customers", content); 76 | 77 | response.EnsureSuccessStatusCode(); 78 | 79 | var responseString = await response.Content.ReadAsStringAsync(); 80 | 81 | Assert.Contains("customerId", responseString); 82 | JObject customer = JsonConvert.DeserializeObject(responseString); 83 | 84 | string customerId = customer["customerId"].Value(); 85 | string accountId = ((JContainer)customer["accounts"]).First["accountId"].Value(); 86 | 87 | return new Tuple(customerId, accountId); 88 | } 89 | 90 | private async Task Deposit(string account, double amount) 91 | { 92 | var json = new 93 | { 94 | accountId = account, 95 | amount = amount, 96 | }; 97 | 98 | string data = JsonConvert.SerializeObject(json); 99 | StringContent content = new StringContent(data, Encoding.UTF8, "application/json"); 100 | 101 | var response = await client.PatchAsync("api/Accounts/Deposit", content); 102 | string result = await response.Content.ReadAsStringAsync(); 103 | 104 | response.EnsureSuccessStatusCode(); 105 | } 106 | 107 | private async Task Withdraw(string account, double amount) 108 | { 109 | var json = new 110 | { 111 | accountId = account, 112 | amount = amount, 113 | }; 114 | 115 | string data = JsonConvert.SerializeObject(json); 116 | StringContent content = new StringContent(data, Encoding.UTF8, "application/json"); 117 | 118 | var response = await client.PatchAsync("api/Accounts/Withdraw", content); 119 | string result = await response.Content.ReadAsStringAsync(); 120 | 121 | response.EnsureSuccessStatusCode(); 122 | } 123 | 124 | private async Task Close(string account) 125 | { 126 | var response = await client.DeleteAsync("api/Accounts/" + account); 127 | response.EnsureSuccessStatusCode(); 128 | } 129 | } 130 | 131 | public static class HttpClientExtensions 132 | { 133 | public static async Task PatchAsync(this HttpClient client, string requestUri, HttpContent content) 134 | { 135 | var method = new HttpMethod("PATCH"); 136 | var request = new HttpRequestMessage(method, requestUri) 137 | { 138 | Content = content 139 | }; 140 | 141 | var response = await client.SendAsync(request); 142 | return response; 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /tests/MyWallet.WebApi.Tests/MyWallet.WebApi.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | PreserveNewest 12 | 13 | 14 | PreserveNewest 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/MyWallet.WebApi.Tests/autofac.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultAssembly": "MyWallet.Infrastructure", 3 | "modules": [ 4 | { 5 | "type": "MyWallet.WebApi.WebApiModule,MyWallet.WebApi", 6 | "properties": { 7 | } 8 | }, 9 | { 10 | "type": "MyWallet.Infrastructure.ApplicationModule", 11 | "properties": { 12 | } 13 | }, 14 | { 15 | "type": "MyWallet.Infrastructure.InMemoryDataAccess.InMemoryModule" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /tests/MyWallet.WebApi.Tests/autofac.production.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultAssembly": "MyWallet.Infrastructure", 3 | "modules": [ 4 | { 5 | "type": "MyWallet.WebApi.WebApiModule,MyWallet.WebApi", 6 | "properties": { 7 | } 8 | }, 9 | { 10 | "type": "MyWallet.Infrastructure.ApplicationModule", 11 | "properties": { 12 | } 13 | }, 14 | { 15 | "type": "MyWallet.Infrastructure.EntityFrameworkDataAccess.EntityFrameworkModule", 16 | "properties": { 17 | "ConnectionString": "Server=localhost;Database=MyWalletDB;User Id=sa;Password=;" 18 | } 19 | } 20 | ] 21 | } --------------------------------------------------------------------------------