Last active
August 29, 2015 14:09
-
-
Save carbonrobot/b92bbad42d441a5088ff to your computer and use it in GitHub Desktop.
Aggregate Service Boundary
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Here we inject the specific data provider and logger | |
// We can easlity swap these out for unit testing, and during runtime they can be injected with any DI framework | |
// The important takeaway is that the PatientService defines it's dependencies and they must be injected. | |
// This ensures there are no 'magic' dependencies that we dont know about outside of the constructor. | |
var data = new Core.Data.SqlDataProvider(); | |
var logging = new Core.Services.LoggingService(); | |
var service = new Core.Services.PatientService(dataProvider, logging); | |
// In the Core.Models assembly, we could manipulate objects to our hearts content, having very granular control | |
// The service is our aggregate boundary to the business objects that we allow our application to work with | |
// Imagine a Fast Food restaraunt. | |
// To order a "Double Cheeseburger" (Domain Model) you talk to the cashier (our Service Layer) | |
// You provide the cashier with required information to complete the order and you receive your order. | |
// The cashier hides the details of creating the Cheeseburger. | |
var cashier = new BurgerKingCashier(); | |
var burger = cashier.Order("Double Cheeseburger", "$7.09"); | |
// An aggregate service hides the details of implementations so it can be shared by all applications | |
// It also provides a convinient single place for things like Error Logging, Authentication, and Authorization | |
// To get a known patient from our service, we would provide the key | |
var patient = service.Get(12); | |
// we can then update and create new patients with standard c# syntax | |
var patient = new Patient("John", "Doe", Symptoms.Cough, Symptoms.Fever); | |
service.Save(patient); | |
var response = service.Get(12); | |
{ | |
HasError: false, | |
Exceptions: null, | |
Result: { Pateitn } | |
} | |
if(response.HasError) | |
return ErrorPage(); | |
// this provides a granular interface to all operations | |
IList<Patient> results = service.FindPatientsByLastName("Doe"); | |
IList<Patient> results = service.Find(x => x.LastName == "Doe" && x.FirstName == "John"); | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// using an aggregate service layer for business operations | |
// sometimes also referred to as "onion architecture", but is effectivly the "service layer" pattern | |
// Note that "Services" does not imply WCF, Web Api, or any other remoting mechanism. The fact that Microsoft calls their | |
// product services confuses the idea of the service as a pattern. In pattern terminology, WCF and Web API are a "Remote Facade". | |
// Core Models, Business objects | |
// references: NONE! | |
Core.Models | |
// Data access, Sql provider logic, mappings, etc | |
// references: Core.Models, Core.Services | |
Core.Data | |
// Services, Aggregate business operations | |
// references: Core.Models | |
Core.Services | |
// NOTE: In the above, we want to ensure that Core.Models requires NO references to Core.Data or Core.Service | |
// This cleanly separates the responsibilities of the assemblies and ensures a good separation of concerns. | |
// Core.Models could have references to other assemblies for various reasons without harm, but | |
// if the Core.Models assembly had a dependency on the Core.Data assembly, then we effectively tie the implementation | |
// of the data provider to the business models. It would then be difficult to change one without affecting the other. | |
// Client application | |
// references: Core.Models, Core.Services, Core.Data | |
CoolCompany.Web | |
// The service contstructor takes an instance of IDataProvider and ILogProvider | |
// Note that the interface is defined in the Services (Consumer) assembly, and not in the Data (provider) assembly. | |
// This "Inversion of Control" decouples the interface from the implementation and allows the consumer | |
// to define the interface instead of the provider defining the interface. This creates a separation of the two | |
// for unit testing and e2e testing, and ensures that each component can change independently of the other | |
class PatientService { | |
private Core.Services.IDataProvider data; | |
private Core.Services.ILogProvider log; | |
public PatientService(IDataProvider data, ILogProvider log){ | |
this.data = data; | |
this.log = log; | |
} | |
Patient Get(int id){ | |
Exec((id) => { | |
return data.GetPatient(12); | |
})) | |
} | |
T Exec<T>(Func<T> action){ | |
try{ | |
return action(); | |
} | |
catch{ | |
Logger.Log(ex); | |
} | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Common issues we are solving with this approach | |
// 1. Incorrect inversion of dependencies | |
// 2. Prolific DTO mapping | |
// In a common n-tier architecture, you might see | |
// 3. Using Sprocs for simple CRUD operations | |
// 4. Using I/O bound operations in a web request | |
// 5. No common operations for db access | |
// 6. Not injectable |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment