Skip to content

Instantly share code, notes, and snippets.

@carbonrobot
Last active August 29, 2015 14:09
Show Gist options
  • Save carbonrobot/b92bbad42d441a5088ff to your computer and use it in GitHub Desktop.
Save carbonrobot/b92bbad42d441a5088ff to your computer and use it in GitHub Desktop.
Aggregate Service Boundary
// 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");
// 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);
}
}
}
}
}
}
// 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