├── README.md ├── Onion.Core ├── Onion.Core.csproj ├── Fare │ ├── IRiderFareCalculator.cs │ ├── IDriverEarningCalculator.cs │ └── FareModel.cs └── GeoCoordinate.cs ├── Onion.DomainServices ├── Fare │ ├── IFareRepository.cs │ ├── IRouteService.cs │ └── RiderFareCalculator.cs └── Onion.DomainServices.csproj ├── Onion.ApplicationServices ├── Onion.ApplicationServices.csproj ├── Route │ └── RouteService.cs └── Fare │ └── FareRepository.cs ├── .gitignore ├── Onion.DomainServices.Tests ├── Onion.DomainServices.Tests.csproj └── RiderFareTests.cs └── Onion.sln /README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Onion.Core/Onion.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Onion.Core/Fare/IRiderFareCalculator.cs: -------------------------------------------------------------------------------- 1 | namespace Onion.Core.Fare 2 | { 3 | public interface IRiderFareCalculator 4 | { 5 | decimal EstimateFare(GeoCoordinate start, GeoCoordinate end); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Onion.DomainServices/Fare/IFareRepository.cs: -------------------------------------------------------------------------------- 1 | using Onion.Core; 2 | using Onion.Core.Fare; 3 | 4 | namespace Onion.DomainServices.Fare 5 | { 6 | public interface IFareRepository 7 | { 8 | FareModel GetFareModel(GeoCoordinate start); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Onion.Core/Fare/IDriverEarningCalculator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Onion.Core.Fare 6 | { 7 | public interface IDriverEarningCalculator 8 | { 9 | decimal CalculateEarning(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Onion.DomainServices/Onion.DomainServices.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Onion.DomainServices/Fare/IRouteService.cs: -------------------------------------------------------------------------------- 1 | using Onion.Core; 2 | 3 | namespace Onion.DomainServices.Fare 4 | { 5 | public interface IRouteService 6 | { 7 | decimal CalculateDistance(GeoCoordinate start, GeoCoordinate end); 8 | 9 | bool InAirportZone(GeoCoordinate start, GeoCoordinate end); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Onion.ApplicationServices/Onion.ApplicationServices.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Onion.Core/GeoCoordinate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Onion.Core 6 | { 7 | public class GeoCoordinate 8 | { 9 | public double Latitude { get; set; } 10 | public double Longitude { get; set; } 11 | 12 | public GeoCoordinate(double lat, double lng) 13 | { 14 | Latitude = lat; 15 | Longitude = lng; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.*~ 3 | project.lock.json 4 | .DS_Store 5 | *.pyc 6 | nupkg/ 7 | 8 | # Visual Studio Code 9 | .vscode 10 | 11 | # Rider 12 | .idea 13 | 14 | # User-specific files 15 | *.suo 16 | *.user 17 | *.userosscache 18 | *.sln.docstates 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | build/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Oo]ut/ 32 | msbuild.log 33 | msbuild.err 34 | msbuild.wrn 35 | 36 | # Visual Studio 2015 37 | .vs/ -------------------------------------------------------------------------------- /Onion.Core/Fare/FareModel.cs: -------------------------------------------------------------------------------- 1 | namespace Onion.Core.Fare 2 | { 3 | public class FareModel 4 | { 5 | public string Description { get; set; } 6 | public decimal Flagfall { get; set; } 7 | public decimal Tariff { get; set; } 8 | public decimal BookingFee { get; set; } 9 | public decimal SoilingFee { get; set; } 10 | public decimal AirportLevy { get; set; } 11 | public decimal RiderCancellationFee { get; set; } 12 | public decimal DriverCancellationFee { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Onion.ApplicationServices/Route/RouteService.cs: -------------------------------------------------------------------------------- 1 | using Onion.Core; 2 | using Onion.DomainServices.Fare; 3 | 4 | namespace Onion.ApplicationServices.Route 5 | { 6 | public class RouteService : IRouteService 7 | { 8 | public decimal CalculateDistance(GeoCoordinate start, GeoCoordinate end) 9 | { 10 | /* Distance calculation is obscured for the sake of brevity. 11 | Typically this would involve geospatial and mathematical algorithms, 12 | and/or the use of an external library such as Google Maps */ 13 | 14 | return 10; 15 | } 16 | 17 | public bool InAirportZone(GeoCoordinate start, GeoCoordinate end) 18 | { 19 | return false; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Onion.DomainServices.Tests/Onion.DomainServices.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Onion.ApplicationServices/Fare/FareRepository.cs: -------------------------------------------------------------------------------- 1 | using Onion.Core; 2 | using Onion.Core.Fare; 3 | using Onion.DomainServices.Fare; 4 | 5 | namespace Onion.ApplicationServices.Fare 6 | { 7 | public class FareRepository : IFareRepository 8 | { 9 | public FareModel GetFareModel(GeoCoordinate coordinate) 10 | { 11 | /* Details of retrieving the Model is obscurred for the sake of brevity. 12 | We would have used the coordinate parameter to figure out appropriate fare models to use, 13 | typically retrieved from a database or similar. */ 14 | 15 | var fareModel = new FareModel() 16 | { 17 | Description = "Standard pricing", 18 | BookingFee = 1, 19 | Flagfall = 3, 20 | Tariff = 2, 21 | }; 22 | 23 | return fareModel; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Onion.DomainServices/Fare/RiderFareCalculator.cs: -------------------------------------------------------------------------------- 1 | using Onion.Core; 2 | using Onion.Core.Fare; 3 | 4 | namespace Onion.DomainServices.Fare 5 | { 6 | public class RiderFareCalculator : IRiderFareCalculator 7 | { 8 | private IRouteService routeService; 9 | private IFareRepository fareRepository; 10 | 11 | public RiderFareCalculator(IRouteService routeService, IFareRepository fareRepository) 12 | { 13 | this.routeService = routeService; 14 | this.fareRepository = fareRepository; 15 | } 16 | 17 | public decimal EstimateFare(GeoCoordinate start, GeoCoordinate end) 18 | { 19 | FareModel model = fareRepository.GetFareModel(start); 20 | 21 | decimal estimate = ApplyBaseFare(model); 22 | estimate = ApplyTariff(estimate, model.Tariff, start, end); 23 | estimate = ApplyAirportLevy(estimate, model.AirportLevy, start, end); 24 | 25 | return estimate; 26 | } 27 | 28 | private decimal ApplyBaseFare(FareModel model) 29 | { 30 | return model.BookingFee + model.Flagfall; 31 | } 32 | 33 | private decimal ApplyTariff(decimal fare, decimal tariff, GeoCoordinate start, GeoCoordinate end) 34 | { 35 | decimal distance = routeService.CalculateDistance(start, end); 36 | 37 | return fare + tariff * distance; 38 | } 39 | 40 | private decimal ApplyAirportLevy(decimal fare, decimal airportLevy, GeoCoordinate start, GeoCoordinate end) 41 | { 42 | bool isAirportTrip = routeService.InAirportZone(start, end); 43 | decimal airportFare = fare + airportLevy; 44 | 45 | return isAirportTrip ? airportFare : fare; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Onion.DomainServices.Tests/RiderFareTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Moq; 3 | using Onion.Core; 4 | using Onion.Core.Fare; 5 | using Onion.DomainServices.Fare; 6 | 7 | namespace Onion.DomainServices.Tests 8 | { 9 | [TestClass] 10 | public class RiderFareTests 11 | { 12 | private Mock fareRepoMock; 13 | private Mock routeServiceMock; 14 | 15 | private FareModel fareModel; 16 | 17 | [TestInitialize] 18 | public void Init() 19 | { 20 | fareRepoMock = new Mock(); 21 | routeServiceMock = new Mock(); 22 | 23 | fareModel = new FareModel() 24 | { 25 | Description = "Standard pricing", 26 | BookingFee = 1, 27 | Flagfall = 3, 28 | Tariff = 2, 29 | AirportLevy = 5 30 | }; 31 | 32 | fareRepoMock.Setup(repo => repo.GetFareModel(It.IsAny())).Returns(fareModel); 33 | } 34 | 35 | [TestMethod] 36 | public void GivenAStandard5KmTrip_ThenEstimate14Dollars() 37 | { 38 | var start = new GeoCoordinate(1, 1); 39 | var end = new GeoCoordinate(2, 2); 40 | decimal distance = 5; 41 | routeServiceMock.Setup(service => service.CalculateDistance(start, end)).Returns(distance); 42 | IRiderFareCalculator target = new RiderFareCalculator(routeServiceMock.Object, fareRepoMock.Object); 43 | 44 | var estimate = target.EstimateFare(start, end); 45 | 46 | Assert.AreEqual(14, estimate); 47 | } 48 | 49 | [TestMethod] 50 | public void GivenAnAirportTrip_ThenEstimate19Dollars() 51 | { 52 | var start = new GeoCoordinate(3, 3); 53 | var end = new GeoCoordinate(4, 4); 54 | decimal distance = 5; 55 | routeServiceMock.Setup(service => service.CalculateDistance(start, end)).Returns(distance); 56 | routeServiceMock.Setup(service => service.InAirportZone(start, end)).Returns(true); 57 | IRiderFareCalculator target = new RiderFareCalculator(routeServiceMock.Object, fareRepoMock.Object); 58 | 59 | var estimate = target.EstimateFare(start, end); 60 | 61 | Assert.AreEqual(19, estimate); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Onion.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28922.388 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Onion.Core", "Onion.Core\Onion.Core.csproj", "{F19BD7E2-501D-4C02-8656-9FAEACD941EC}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Onion.DomainServices", "Onion.DomainServices\Onion.DomainServices.csproj", "{9A63961A-8C43-4C96-B502-BA80B9B71E36}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Onion.ApplicationServices", "Onion.ApplicationServices\Onion.ApplicationServices.csproj", "{7A460986-8FB9-4B64-B392-4437312CB308}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{15379299-B9D3-4DC4-BF41-6A35851A5CF6}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{80F44FA3-4E9F-49D9-B182-30F98490D8CF}" 15 | ProjectSection(SolutionItems) = preProject 16 | .gitignore = .gitignore 17 | README.md = README.md 18 | EndProjectSection 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Onion.DomainServices.Tests", "Onion.DomainServices.Tests\Onion.DomainServices.Tests.csproj", "{4E208D16-38EA-42E7-8AB8-A1307C58F10D}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {F19BD7E2-501D-4C02-8656-9FAEACD941EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {F19BD7E2-501D-4C02-8656-9FAEACD941EC}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {F19BD7E2-501D-4C02-8656-9FAEACD941EC}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {F19BD7E2-501D-4C02-8656-9FAEACD941EC}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {9A63961A-8C43-4C96-B502-BA80B9B71E36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {9A63961A-8C43-4C96-B502-BA80B9B71E36}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {9A63961A-8C43-4C96-B502-BA80B9B71E36}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {9A63961A-8C43-4C96-B502-BA80B9B71E36}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {7A460986-8FB9-4B64-B392-4437312CB308}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {7A460986-8FB9-4B64-B392-4437312CB308}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {7A460986-8FB9-4B64-B392-4437312CB308}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {7A460986-8FB9-4B64-B392-4437312CB308}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {4E208D16-38EA-42E7-8AB8-A1307C58F10D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {4E208D16-38EA-42E7-8AB8-A1307C58F10D}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {4E208D16-38EA-42E7-8AB8-A1307C58F10D}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {4E208D16-38EA-42E7-8AB8-A1307C58F10D}.Release|Any CPU.Build.0 = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | GlobalSection(NestedProjects) = preSolution 49 | {4E208D16-38EA-42E7-8AB8-A1307C58F10D} = {15379299-B9D3-4DC4-BF41-6A35851A5CF6} 50 | EndGlobalSection 51 | GlobalSection(ExtensibilityGlobals) = postSolution 52 | SolutionGuid = {EAD47C96-5D2E-4A41-B2F2-C2C8FD6CDA20} 53 | EndGlobalSection 54 | EndGlobal 55 | --------------------------------------------------------------------------------