├── 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 |
--------------------------------------------------------------------------------