Repositories in domain driven design - c#

I've been struggling with DDD for several months now and, while I think I have a reasonably good idea about some of the concepts I'm less confident about how implementation should work, specifically how I should load data from the database. It doesn't help that I've only been working in C# (spottily) for six months.
[ORIGINAL QUESTION - SEE UPDATE BELOW]
In the app I've started working on, I have a domain namespace that contains base classes, a service namespace that performs operations using those base class, and then a repository and DAL namespace to connect to the database.
I thought the easiest way to proceed would be to use inheritance in the service namespace to add procedures like LoadFromDb, but when I start implementing I'm finding that this method typically requires the most code because I have to assign all the class properties twice (once in the repository namespace and then in the service namespace).
Here's an example. I can get Option 2 and Option 3 to work, but I was hoping for something closer in spirit to Option 1.
namespace Domain
{
public class Request
{
public int RequestID{get; set;}
public string RequestingUser {get; set;}
public string Title {get; set;}
public string Description{get; set;}
public string status {get; set;}
}
}
namespace app
{
class MyApp
{
void Main()
{
//option 1
Domain.Request x = new Service.svcRequest(5);
//option 2
Domain.Request y = new Service.svcRequest(5);
//option 3
Domain.Request z = new Domain.Request();
Service.svcRequest2.loadRequest(5, z);
}
}
}
namespace Service
{
public class svcRequest : Domain.Request
{
public svcRequest(int RequestID)
{
//this is what I want to do.
// It fails because "this" is read-only
// and because "this" can't be implicitly converted to DomainRequest.
this = (Domain.Request)repos.Loads.LoadRequest(RequestID);
//option 2, which is what I'm doing instead for now, but when you get
// to 20 or 50 properties, it's a bit much,
// esp. since those properties have already been assigned once
// within the repository namespace.
Domain.Request MyRequest = repos.Loads.LoadRequest(RequestID);
this.RequestID = MyRequest.RequestID;
this.RequestingUser = MyRequest.RequestingUser;
this.Title = MyRequest.Title;
this.Description = MyRequest.Description;
this.status = MyRequest.status;
}
}
public class svcRequest2
{
//option 3. Much less code, but now I'm not really using inheritance,
// so in my application layer I can't just declare my variable
// and use the svcRequest constructor
public static void loadRequest(int RequestID, Domain.Request MyRefRequest)
{
MyRefRequest = (Domain.Request)repos.Loads.LoadRequest(RequestID);
}
}
}
namespace repos
{
public static class Loads
{
public static Domain.Request LoadRequest(int RequestID)
{
Domain.Request MyRequest = new Domain.Request();
DataRow MyRow = dal.Loads.LoadRequestRow(RequestID);
MyRequest.RequestID = RequestID;
MyRequest.RequestingUser = (string)MyRow["User"];
MyRequest.Title = (string)MyRow["Title"];
MyRequest.Description = (string)MyRow["Description"];
MyRequest.status = (string)MyRow["Status"];
return MyRequest;
}
}
}
namespace dal
{
public static class Loads
{
public static DataRow LoadRequestRow(int RequestID)
{
OleDbConnection dbCon = new OleDbConnection("Provider=SQLOLEDB;Data Source=dbServer;Initial Catalog=RequestDB;User ID=Joe;Password=password");
string Select = "Select * from RequestTable where ID = " + RequestID;
OleDbDataAdapter dbRequest = new OleDbDataAdapter(Select, dbCon);
DataSet dsRequest = new DataSet();
dbRequest.Fill(dsRequest);
DataRow drRequest = dsRequest.Tables[0].Rows[0];
return drRequest;
}
}
}
[UPDATE]
Here's a second try. I renamed my Domain namespace to Model, and renamed my Service namespace to Domain, which I think more closely fits the DDD conventions. As suggested, I added an interface in the repository namespace that I used in the DAL. The only thing I can't get to work right now is option 1's Load statement, but I think I just need to research inheritance a bit more.
Am I getting closer?
using System;
using System.Data;
using System.Data.OleDb;
namespace Model
{
public class Request
{
public int RequestID{get; set;}
public string RequestingUser {get; set;}
public string Title {get; set;}
public string Description{get; set;}
public string status {get; set;}
}
}
namespace App
{
class MyApp
{
void Main()
{
//option 1
Model.Request x = new Domain.dmnRequest(5);
//option 2
Model.Request y = new Domain.dmnRequest2(5);
//option 3
Model.Request z = new Model.Request();
Domain.dmnRequest3.loadRequest(5, z);
}
}
}
namespace Domain
{
public class dmnRequest : Model.Request, dal.Request
{
public dmnRequest(int requestID)
{
//this is what I want to do. I'm not sure why it's failing
Load(requestID);
}
}
public class dmnRequest2 : Model.Request
{
public dmnRequest2(int requestID)
{
//option 2; it works but is cumbersome after you hit the 20th property
dal.Request tmpRequest = new dal.Request();
tmpRequest.Load(requestID);
this.RequestID = tmpRequest.RequestID;
this.RequestingUser = tmpRequest.RequestingUser;
this.Title = tmpRequest.Title;
this.Description = tmpRequest.Description;
this.status = tmpRequest.status;
}
}
public class dmnRequest3
{
//option 3. Much less code, but now I'm not really using inheritance, so in my application layer I can't just declare my variable and use the dmnRequest constructor
public static void loadRequest(int RequestID, Model.Request MyRequest)
{
dal.Request dalRequest = (dal.Request)MyRequest;
dalRequest.Load(RequestID);
MyRequest = (Model.Request)dalRequest;
}
}
}
namespace repos
{
public interface SaveMe {void Save(int ID); }
public interface LoadMe {void Load(int ID); }
}
namespace dal
{
public class Request : Model.Request, repos.LoadMe
{
public void Load(int requestID)
{
OleDbConnection dbCon = new OleDbConnection("yaddayadda");
string Select = "Select * from RequestTable where ID = " + requestID.ToString();
OleDbDataAdapter dbRequest = new OleDbDataAdapter(Select, dbCon);
DataSet dsRequest = new DataSet();
dbRequest.Fill(dsRequest);
DataRow drRequest = dsRequest.Tables[0].Rows[0];
this.RequestID = requestID;
this.RequestingUser = (string)drRequest["User"];
this.Title = (string)drRequest["Title"];
this.Description = (string)drRequest["Description"];
this.status = (string)drRequest["Status"];
}
}
}

You got it all wrong. In DDD things are pretty simple. The Domain knows only about the repository interface which gets implemented by an actually repository class in the Persistence Layer (DAL). The repository works with the database to save/load domain objects (in DDD those domain objects should be aggregate roots).
Nothing should be static here and the repository should get all the data it needs from the db and then use that to restore an object. The repository ALWAYS returns a Domain Entity, never a data row, data table, Entity Framework entities etc. That's because the purpose of the Repository is to decouple the Domain from the persistence details.
Simply put, the Domain just says: "Hey repository give me that BsuinessEntity with this id". The Domain tells the repository what to get and never how to get it. The domain doesn't really know there is a database involved. All it sees it's an abstraction (the repository interface) working with objects the Domain knows about.
The point of all this is to respect the Separation of Concerns. The Domain cares about business concepts and use cases, while the repository cares about storing/retrieving objects from the db.

Related

C# Architecture/Pattern for Tenant-Specific Business Logic

Say you have a multi-tenant app. A Tenant has various properties:
public class Tenant{
public string TenantName {get; set;}
public string TenantUrl {get; set;}
}
This way when my service layer sends emails, for example, I can do the following:
SendEmail(Tenant.FromEmailAddress, recipientEmailAddress)
This works well for properties. In many places throughout my business logic, I'm encountering cases where tenant-specific behaviors must be accounted for. One example is retrieving photos for the homepage:
public List<string> GetPhotoUrls(){
if(currentTenant == TenantA){
// logic to go off to retrieve from one third party
} else if (currentTenant == TenantB){
// totally different logic
} else... // one for each tenant
// do some stuff
// return stuff
}
GetPhotoUrls is a simple example - but there are cases like this in many places in my business logic. I'm looking for a simple pattern where I can define and implement tenant-specific logic. The overall goal is to get all tenant-specific logic in one place so tenant creation and definition is easy.
I would like the developer experience to read along the lines of:
public List<string> GetPhotoUrls(){
currentTenant.GetPhotoUrls(); // define this logic on the tenant object somehow
// do some stuff
// return stuff
}
What patterns/constructs are available to achieve this?
Use the strategy pattern in your case. The pattern is best applied when you see switch statements or multiple if statements to simplify the client so that it delegates custom implementation to dependent interfaces. You may also use in combination of factory pattern. To illustrate this:
public interface ITenant{
List<string> GetPhotoUrls();
}
public class TenantA:ITenant{
public string TenantName {get; set;}
public string TenantUrl {get; set;}
public List<string> GetPhotoUrls(){
//A implementation
}
}
public class TenantB:ITenant{
public string TenantName {get; set;}
public string TenantUrl {get; set;}
public List<string> GetPhotoUrls(){
//B implementation
}
}
public class SomeTenantApp{
public SomeTenantApp(ITenant tenant){
_tenant = tenant;
}
public void DoSomething(){
var urls = _tenant.GetPhotoUrls();
//do something
}
}
public static class TenantFactory{
public static ITenant Create(string id)
{
//logic to get concrete tenant
return concreteTenant;
}
}
class Program
{
static void Main(string[] args)
{
var tenant = TenantFactory.Create("A");
var app = var SomeTenantApp(tenant);
app.DoSomething();
}
}
The client (SomeTenantApp) won't have to change. You delegated the implementation to the concrete class which owns the logic.
If you want to build SaaS, I'd strongly recommend using ASP.NET Core and dependency injection to overcome multi-tenancy issue.
You can defined your tenant class :
public class AppTenant
{
public string Name { get; set; }
public string[] Hostnames { get; set; }
}
Next you can resolve a tenant from the current request
public class AppTenantResolver : ITenantResolver<AppTenant>
{
IEnumerable<AppTenant> tenants = new List<AppTenant>(new[]
{
new AppTenant {
Name = "Tenant 1",
Hostnames = new[] { "localhost:6000", "localhost:6001" }
},
new AppTenant {
Name = "Tenant 2",
Hostnames = new[] { "localhost:6002" }
}
});
public async Task<TenantContext<AppTenant>> ResolveAsync(HttpContext context)
{
TenantContext<AppTenant> tenantContext = null;
// it's just a sample...
var tenant = tenants.FirstOrDefault(t =>
t.Hostnames.Any(h => h.Equals(context.Request.Host.Value.ToLower())));
if (tenant != null)
{
tenantContext = new TenantContext<AppTenant>(tenant);
}
return tenantContext;
}
}
Wiring it up :
public void ConfigureServices(IServiceCollection services)
{
services.AddMultitenancy<AppTenant, AppTenantResolver>();
}
Getting the current tenant (whenever you need it) :
public class HomeController : Controller
{
private AppTenant tenant;
public HomeController(AppTenant tenant)
{
this.tenant = tenant;
}
.
.
.
}
For more info take a look at SaasKit
Building multi-tenant applications with ASP.NET Core (ASP.NET 5)

Dependency Injection using Ninject without interface

We are working on a Mvc application, where we want to use dependency injection using nInject. Currently we are maintaining entities in different class library "ShopEntities" and in our mvc application we are using this entities.
Let's consider a class in ShopEntities.
namespace ShopEntities
{
public class Customers
{
public int custId {get;set;}
public string custName {get;set;}
public string Address {get;set;}
public string ShippingAddress {get;set;}
}
}
Now when we want to use it in our mvc application, we create an instance and set the properties like below,
public ActionResult Index()
{
ShopEntities.Customers cust = new ShopEntities.Customers();
cust.CustName = "Sam";
cust.IAddress = "xyz";
cust.ShippingAddress = "xyz xyx xyz";
}
How to use nInject here to avoid dependency? Further we don't want to create interfaces as this is limited in scope. Thanks in advance.
The way to abstract away the use of the Customer entity from the presentation layer is not to hide the entity itself behind an ICustomer of some sort, nor to let a DI container build it up. Hiding data objects behind an interfaces is typically not useful; interfaces are meant to abstract behavior, not data.
As NightOwl already stated, your Customer entity is runtime data and you should not use a container to build up object graphs containing runtime data.
Instead, you should hide specific business operations behind an abstraction. Such abstraction can be consumed by the presentation layer and implemented by the business layer. For instance:
public interface ICustomerServices
{
void CreateCustomer(string customerName, string homeAddress,
string shippingAddress);
void ChangeShippingAddress(Guid customerId, string shippingAddress);
}
Your controller can depend on this abstraction:
private readonly ICustomerServices customerServices;
public CustomerController(ICustomerServices customerServices) {
this.customerServices = customerServices;
}
public ActionResult Index()
{
this.customerServices.CreateCustomer("Sam", "xyz", "xyz xyz xyz");
}
Now your business layer can create an implementation for this abstraction that uses the entities internally:
public class CustomerServices : ICustomerServices
{
private readonly EntitiesContext context;
public CustomerServices(EntitiesContext context) {
this.context = context;
}
public void CreateCustomer(string customerName, string homeAddress,
string shippingAddress)
{
// NOTE that I renamed 'Customers' to 'Customer', since it holds information
// to only one customer. 'Customers' implies a collection.
Customer cust = new ShopEntities.Customer();
cust.CustName = "Sam";
cust.IAddress = "xyz";
cust.ShippingAddress = "xyz xyx xyz";
this.context.Customers.Add(cust);
this.context.SubmitChanges();
}
public void ChangeShippingAddress(...) { ... }
}
Doing this has the advantage that you can keep your presentation layer thin, but there are still quite some down sides to the shown approach, compared to alternatives. One of such alternatives is using a message based approach with SOLID design, as explained here.
If i understand you question, you should create middle business layer to convert ShopEntities to your own Entities:
namespace MyShopEntities
{
public class MyCustomers
{
public int custId {get;set;}
public string custName {get;set;}
public string Address {get;set;}
public string ShippingAddress {get;set;}
}
}
public ActionResult Index()
{
ShopEntities.Customers cust = new MyShopEntities.MyCustomers();
cust.CustName = "Sam";
cust.IAddress = "xyz";
cust.ShippingAddress = "xyz xyx xyz";
}
class BussinesModel
{
void Insert(ShopEntities.Customer customer)
{
// use ShopEntities.Customer only in wrapper
// if you later switch to another Customer dependency,
// you just change this wrapper
MyShopEntities.MyCustomers cust = new MyShopEntities.MyCustomers();
cust.CustName = customer.CustName;
cust.IAddress = customerIAddress;
cust.ShippingAddress = customer.ShippingAddress;
InsertInternal(cust);
}
void InsertInternal(MyShopEntities.MyCustomer customer)
{
// use MyCustomer for all your bussines logic
}
}

TDD Inserting to Database

I've been reading about TDD and I've seen a lot of posts about not to do any database transaction because "single, isolated block of code with no dependencies".
So now I have a little bit of dilemma - I want to be able to test if my service layer method called AddNewStudent actually works. This method goes into my DbContext and then add a new record into the database. If database operations aren't recommended for TDD then how else can I test AddNewStudent method aside from just testing my application on a browser?
public class StudentManager : ManagerBase
{
internal StudentManager() { }
public Student AddNewStudent(string fName, string lName, DateTime dob)
{
// Create a student model instance using factory
var record = Factories.StudentFac.CreateOne(fName, lName, dob);
DbContext.Students.Add(record);
DbContext.SaveChanges();
return record;
}
}
And my test looks like this
[TestMethod]
public void StudentManager_AddNewStudent_Test()
{
var fName = "Ryan";
var lName = "Rigil";
var dob = DateTime.Parse("3/1/2006");
var student = Managers.StudentManager.AddNewStudent(fName, lName, dob);
Assert.AreEqual(fName, student.FirstName);
Assert.AreEqual(lName, student.LastName);
Assert.AreEqual(dob.ToShortDateString(), student.DoB.ToShortDateString());
}
Your StudentManager has dependencies buried internally that make it difficult to test. Consider restructuring your design to allow for better testability.
Looking at the StudentManager the following assumptions were derived...
//An assumed abstraction of the ManagerBase
public abstract class ManagerBase {
public ManagerBase(IDbContext dbContext, IFactory factories) {
DbContext = dbContext;
Factories = factories;
}
public IDbContext DbContext { get; private set; }
public IFactory Factories { get; private set; }
}
//An abstraction of what the unit of work would look like
public interface IDbContext {
//student repository
DbSet<Student> Students { get; }
//...other repositories
int SaveChanges();
}
//Just an example of the Student Factory.
public interface IModelFactory<T> where T : class, new() {
T Create(Action<T> configuration);
}
public interface IFactory {
IModelFactory<Student> StudentFac { get; }
//...other factories. Should try to make your factories Generic
}
With that the target class refactors to...
public class StudentManager : ManagerBase {
public StudentManager(IDbContext dbContext, IFactory factories) : base(dbContext, factories) { }
public Student AddNewStudent(string fName, string lName, DateTime dob) {
// Create a student model instance using factory
var record = Factories.StudentFac.Create(r => {
r.FirstName = fName;
r.LastName = lName;
r.DoB = dob;
});
base.DbContext.Students.Add(record);
base.DbContext.SaveChanges();
return record;
}
}
While it may look like much it will greatly help the testability of your code.
A mocking framework like Moq can now be used to create a fake version of the database access and factories...
[TestMethod]
public void StudentManager_Should_AddNewStudent() {
//Arrange: setup/initialize the dependencies of the test
var fName = "Ryan";
var lName = "Rigil";
var dob = DateTime.Parse("3006-01-03");
//using Moq to create mocks/fake of dependencies
var dbContextMock = new Mock<IDbContext>();
//Extension method used to create a mock of DbSet<T>
var dbSetMock = new List<Student>().AsDbSetMock();
dbContextMock.Setup(x => x.Students).Returns(dbSetMock.Object);
var factoryMock = new Mock<IFactory>();
factoryMock
.Setup(x => x.StudentFac.Create(It.IsAny<Action<Student>>()))
.Returns<Action<Student>>(a => {
var s = new Student();
a(s);
return s;
});
//this is the system/class under test.
//while this is being created manually, you should look into
//using DI/IoC container to manage Dependency Injection
var studentManager = new StudentManager(dbContextMock.Object, factoryMock.Object);
//Act: here we actually test the method
var student = studentManager.AddNewStudent(fName, lName, dob);
//Assert: and check that it executed as expected
Assert.AreEqual(fName, student.FirstName);
Assert.AreEqual(lName, student.LastName);
Assert.AreEqual(dob.ToShortDateString(), student.DoB.ToShortDateString());
}
For production you can create proper implementations of the interfaces and inject them into the classes that depend on them. This answer is based entirely on the example you provided in your post. Take some time to understand the concepts used and also do some more research online. You can then apply these concepts with the remainder of your project as you progress with TDD.

DDD Approach to Access External Information

I have an existing bank application classes as shown below. The banks account can be of SavingsBankAccount or FixedBankAccount. There is an operation called IssueLumpSumInterest. For FixedBankAccount, the balance need to be updated only if the owner of the account has no other account.
This demands the FixedBankAccount object to know about other accounts of the account owner. How to do this by following SOLID/DDD/GRASP/Information Expert pattern?
namespace ApplicationServiceForBank
{
public class BankAccountService
{
RepositoryLayer.IRepository<RepositoryLayer.BankAccount> accountRepository;
ApplicationServiceForBank.IBankAccountFactory bankFactory;
public BankAccountService(RepositoryLayer.IRepository<RepositoryLayer.BankAccount> repo, IBankAccountFactory bankFact)
{
accountRepository = repo;
bankFactory = bankFact;
}
public void IssueLumpSumInterest(int acccountID)
{
RepositoryLayer.BankAccount oneOfRepositroyAccounts = accountRepository.FindByID(p => p.BankAccountID == acccountID);
int ownerID = (int) oneOfRepositroyAccounts.AccountOwnerID;
IEnumerable<RepositoryLayer.BankAccount> accountsForUser = accountRepository.FindAll(p => p.BankUser.UserID == ownerID);
DomainObjectsForBank.IBankAccount domainBankAccountObj = bankFactory.CreateAccount(oneOfRepositroyAccounts);
if (domainBankAccountObj != null)
{
domainBankAccountObj.BankAccountID = oneOfRepositroyAccounts.BankAccountID;
domainBankAccountObj.AddInterest();
this.accountRepository.UpdateChangesByAttach(oneOfRepositroyAccounts);
//oneOfRepositroyAccounts.Balance = domainBankAccountObj.Balance;
this.accountRepository.SubmitChanges();
}
}
}
public interface IBankAccountFactory
{
DomainObjectsForBank.IBankAccount CreateAccount(RepositoryLayer.BankAccount repositroyAccount);
}
public class MySimpleBankAccountFactory : IBankAccountFactory
{
public DomainObjectsForBank.IBankAccount CreateAccount(RepositoryLayer.BankAccount repositroyAccount)
{
DomainObjectsForBank.IBankAccount acc = null;
if (String.Equals(repositroyAccount.AccountType, "Fixed"))
{
acc = new DomainObjectsForBank.FixedBankAccount();
}
if (String.Equals(repositroyAccount.AccountType, "Savings"))
{
//acc = new DomainObjectsForBank.SavingsBankAccount();
}
return acc;
}
}
}
namespace DomainObjectsForBank
{
public interface IBankAccount
{
int BankAccountID { get; set; }
double Balance { get; set; }
string AccountStatus { get; set; }
void FreezeAccount();
void AddInterest();
}
public class FixedBankAccount : IBankAccount
{
public int BankAccountID { get; set; }
public string AccountStatus { get; set; }
public double Balance { get; set; }
public void FreezeAccount()
{
AccountStatus = "Frozen";
}
public void AddInterest()
{
//TO DO: Balance need to be updated only if the person has no other accounts.
Balance = Balance + (Balance * 0.1);
}
}
}
READING
Issue in using Composition for β€œis – a β€œ relationship
Implementing Business Logic (LINQ to SQL)
http://msdn.microsoft.com/en-us/library/bb882671.aspx
Architecting LINQ to SQL applications
Exploring N-Tier Architecture with LINQ to SQL
http://randolphcabral.wordpress.com/2008/05/08/exploring-n-tier-architecture-with-linq-to-sql-part-3-of-n/
Confusion between DTOs (linq2sql) and Class objects!
Domain Driven Design (Linq to SQL) - How do you delete parts of an aggregate?
The first thing I noticed was the improper use of the bank account factory. The factory, pretty much as you have it, should be used by the repository to create the instance based on the data retrieved from the data store. As such, your call to accountRepository.FindByID will return either a FixedBankAccount or SavingsBankAccount object depending on the AccountType returned from the data store.
If the interest only applies to FixedBankAccount instances, then you can perform a type check to ensure you are working with the correct account type.
public void IssueLumpSumInterest(int accountId)
{
var account = _accountRepository.FindById(accountId) as FixedBankAccount;
if (account == null)
{
throw new InvalidOperationException("Cannot add interest to Savings account.");
}
var ownerId = account.OwnerId;
if (_accountRepository.Any(a => (a.BankUser.UserId == ownerId) && (a.AccountId != accountId)))
{
throw new InvalidOperationException("Cannot add interest when user own multiple accounts.");
}
account.AddInterest();
// Persist the changes
}
NOTE: FindById should only accept the ID parameter and not a lambda/Func. You've indicated by the name "FindById" how the search will be performed. The fact that the 'accountId' value is compared to the BankAccountId property is an implementation detail hidden within the method. Name the method "FindBy" if you want a generic approach that uses a lambda.
I would also NOT put AddInterest on the IBankAccount interface if all implementations do not support that behavior. Consider a separate IInterestEarningBankAccount interface that exposes the AddInterest method. I would also consider using that interface instead of FixedBankAccount in the above code to make the code easier to maintain and extend should you add another account type in the future that supports this behavior.
From reading your requirement, here is how I would do it:
//Application Service - consumed by UI
public class AccountService : IAccountService
{
private readonly IAccountRepository _accountRepository;
private readonly ICustomerRepository _customerRepository;
public ApplicationService(IAccountRepository accountRepository, ICustomerRepository customerRepository)
{
_accountRepository = accountRepository;
_customerRepository = customerRepository;
}
public void IssueLumpSumInterestToAccount(Guid accountId)
{
using (IUnitOfWork unitOfWork = UnitOfWorkFactory.Create())
{
Account account = _accountRepository.GetById(accountId);
Customer customer = _customerRepository.GetById(account.CustomerId);
account.IssueLumpSumOfInterest(customer);
_accountRepository.Save(account);
}
}
}
public class Customer
{
private List<Guid> _accountIds;
public IEnumerable<Guid> AccountIds
{
get { return _accountIds.AsReadOnly();}
}
}
public abstract class Account
{
public abstract void IssueLumpSumOfInterest(Customer customer);
}
public class FixedAccount : Account
{
public override void IssueLumpSumOfInterest(Customer customer)
{
if (customer.AccountIds.Any(id => id != this._accountId))
throw new Exception("Lump Sum cannot be issued to fixed accounts where the customer has other accounts");
//Code to issue interest here
}
}
public class SavingsAccount : Account
{
public override void IssueLumpSumOfInterest(Customer customer)
{
//Code to issue interest here
}
}
The IssueLumpSumOfInterest method on the Account aggregate requires the Customer aggregate to help decide whether interest should be issued.
The customer aggregate contains a list of account IDs - NOT a list of account aggregates.
The base class 'Account' has a polymorphic method - the FixedAccount checks that the customer doesn't have any other accounts - the SavingsAccount doesn't do this check.
2 min scan answer..
Not sure why there is a need for 2 representations of a BankAccount
RepositoryLayer.BankAccount and DomainObjectsForBank.IBankAccount. Hide the persistence layer coupled one.. deal with just the domain object in the service.
Do not pass/return Nulls - I think is good advice.
The finder methods look like the LINQ methods which select items from a list of collection. Your methods look like they want to get the first match and exit..in which case your parameters can be simple primitives (Ids) vs lambdas.
The general idea seems right. The service encapsulates the logic for this transaction - not the domain objects. If this changes, only one place to update.
public void IssueLumpSumInterest(int acccountID)
{
var customerId = accountRepository.GetAccount(accountId).CustomerId;
var accounts = accountRepository.GetAccountsForCustomer(customerId);
if ((accounts.First() is FixedAccount) && accounts.Count() == 1)
{
// update interest
}
}
Things that strike me as weird:
Your IBankAccount has a method FreezeAccount, but I presume that all accounts would have quite similar behavior? Perhaps a BankAccount class is warranted that implements some of the interface?
AccountStatus should probably be an enum? What should happen if an account is "Forzen"?

c# object/design fundamentals

Is this a standard, good practice way of doing things? Basically return a list of itself? Should the actual fields (id, title, etc) be a separate class? (I've seen people call it DTO objects)
I'm starting a project & I want to try & get some of these fundamentals down--
Thanks!!
public class Calendar
{
public int id { get; set; }
public string title { get; set; }
public List<calendar> GetAll()
{
var list = new List<calendar>();
var db = new mssql2();
db.set("s1");
string sql = #"select * from [cal]";
var dr = db.dr(sql);
while (dr.Read())
{
var e = new calendar();
e.id = (int)dr["id"];
e.title = dr["title"].ToString();
list.Add(e);
}
return list;
}
}
You seem to be mixing your Domain model with your Data Access layer.
Keep Calendar as it's own class, and maybe make another class called CalendarService or CalendarRepository that returns you a list of Calendar objects.
Here is an example:
public class Calendar
{
public Calendar() { }
public Calendar(int id, string title)
{
Id = id;
Title = title;
}
public int Id { get; set; }
public string Title { get; set; }
}
public class CalendarService
{
public static List<Calendar> GetAll()
{
var list = new List<Calendar>();
var db = new mssql2();
db.set("s1");
string sql = #"select * from [cal]";
var dr = db.dr(sql);
while (dr.Read())
{
// Use the constructor to create a new Calendar item
list.Add(new Calendar((int)dr["id"], dr["title"].ToString()));
}
return list;
}
}
The general idea is for the classes to represent domain objects, and class members various properties of those domain objects. Class functions would represent what the objects can do.
In your case, it might be more fitting to remove the get_all() to a some class abstracting database operations. Calendar would have the functionalities of a calendar (getting/setting some dates, getting skip years, getting/setting some appointments); depending of what you want to accomplish with a calendar.
You're tightly coupling data access, and your "get_all" method isn't even using anything from the object of type calendar. If, as in this case, your method doesn't use any data from the instance of the class to which it belongs, then that method should either not be there, or should be a static method. My preference would be for the former -- have a class whose intent is to retrieve a calendar or calendars from the database. It is a more sensible organization of code, is more testable, can be more easily abstracted from the data layer, and it also makes your data object more portable.

Categories