DDD logic and Persistence Ignorance - c#

Consider the following scenario:
public class Document
{
private ISet<User> sharedWith;
public Document(string name)
{
this.sharedWith = new HashSet<User>();
this.Name = name;
}
public string Name { get; private set; }
public IEnumerable<User> SharedWith
{
get
{
return this.sharedWith;
}
}
public void ShareWith(User user)
{
if (this.SharedWith.Contains(user))
{
throw new Exception("This document is already shared with that user.");
}
this.sharedWith.Add(user);
}
}
Documents can be shared with User
When sharing a document with a user, if the document has already been shared with that user, throw an exception.
Documents can be shared with 10's of thousands of users.
Obviously this does not scale very well, because of the need to check the SharedWith for user existence, resulting in the ORM lazy-loading the entire collection into memory. I could do the existence check in the application service, but I consider this domain logic, and so it makes the most sense to me that I keep it in the Document class.
I can't seem to figure out how this should be done with DDD? And what if I am unable to use an ORM, how does one do this sort of stuff?
I suppose I should have a Document Aggregate and a User Aggregate?
I've looked at various DDD resources (I have not read the book though), but I can't seem to find an answer to this particular scenario.

this was quickly done up so it's not perfect but you get the gist of it:
public class User { public Guid UserId { get; set; } }
public class Document
{
public string Name { get; private set; }
private ICollection<User> sharedWith = new List<User>();
private DateTime? publishedOn;
public Document(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("Name is required");
}
this.Name = name;
}
public void Publish()
{
if (this.publishedOn.HasValue == false)
{
this.publishedOn = DateTime.UtcNow;
}
}
public void SharedWith(User user)
{
if (this.publishedOn.HasValue == false)
{
throw new InvalidOperationException("Document must be published for sharing is allowed.");
}
sharedWith.Add(user);
}
}
public interface IDocumentRepository
{
Document documentOfId(Guid id);
bool IsAlreadySharedWith(Guid documentId, Guid userId);
}
public interface IUseRepository
{
User userOfId(Guid id);
}
public class ShareDocumentService
{
private readonly IUseRepository userRepository;
private readonly IDocumentRepository documentRepository;
public void ShareWith(Guid userId, Guid documentId)
{
if (documentRepository.IsAlreadySharedWith(documentId, userId))
throw new InvalidOperationException("Document has already been shared with user.");
User user = userRepository.userOfId(userId);
Document doc = documentRepository.documentOfId(documentId);
doc.SharedWith(user);
}
}

I suppose if you were modelling this in a paper/actor based world, then someone would have the job of marshalling who has access to which document(s) and this would probably rely on some sort of paper based artefact. To gain access to a document you would have to fill out a Document Request Form, which might go through an approval process.
This form in a paper based world, would become the many-to-many linking entity that becomes the key to user's accessing secure documents. It would make User, Document and DocumentRequestForm three separate entities.

Related

How to access CRUD operations of single db class instance without static methods

I'm new to NoSql and MongoDB. I'm using the MongoDB C# driver inside Visual Studio.
I've read in different places that it's preferable to have a single instance of your database class that maintains the connection(s) to keep everything thread safe and that it's generally a bad idea to use static classes for database CRUD operations.
At the start of my program I instantiate my database class which opens a connection. Within that class and also in derived classes I can perform CRUD operations. But now I'm in a different part of my solution (same namespace, different class) and I need to do read operations to check if a user exists. I also need to compose a new document that I then want to insert.
Now I'm in a situation where that's only possible by creating a new instance of the database class to access its CRUD methods. I want to avoid static CRUD methods (that could be accessed from other classes) because then the base class of my database connection also needs to be static. I cannot figure out how to approach this and what would be the recommended way.
From the MongoDB website:
The MongoClient instance actually represents a pool of connections to the database; you will only need one instance of class MongoClient even with multiple threads.
http://mongodb.github.io/mongo-csharp-driver/2.2/getting_started/quick_tour/
Does this mean I should create a new MongoClient everytime I need to acces the database in others parts of my program?
UPDATE
It seems I was a bit mistaken about the static properties and how they can be used. I now have it setup like this:
class Database
{
const string MongoConnection = "mongodb+srv://user:password#cluster.mongodb.net";
public static MongoClient Client { get; set; }
public static IMongoDatabase Directory { get; set; }
public static IMongoCollection<User> Collection { get; set; }
public Database()
{
Client = new MongoClient(MongoConnection);
Directory= Client.GetDatabase("studentDB");
Collection = Directory.GetCollection<User>("users");
}
public static void InsertNewUser(User user)
{
Collection.InsertOne(user);
}
public static bool EmailHasAccount(string email)
{
return Collection.Find(x => x.Email == email).FirstOrDefault() == null ? false : true;
}
public static User RetrieveUserAccount(string email)
{
return Collection.Find(x => x.Email == email).FirstOrDefault();
}
}
public class User
{
public Guid Id { get; private set; }
public string Name { get; set; }
public string Email { get; set; }
public User(string name, string email)
{
Id = Guid.NewGuid();
Name = name;
Email = email;
}
}
And in my main program I can use it like this:
var db = new Database();
var user = new User("myName", "email#address");
Database.InsertNewUser(user);
Console.WriteLine(Database.EmailHasAccount("email#address")); // returns true
Console.WriteLine(Database.RetrieveUserAccount("email#address").Name); // returns "myName"
That's exactly what I was looking for. What would be the best way to handle multiple collections? Would it be safe to change the Collection property or is it better to create separate properties? Is a Generic even possible?

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)

Accessing a list of objects & how to claim a object for the fatest request

I'm developing an Asp.net mvc project:
There is a List store all the online Pupils
There are few methods:
- Login(Pupil p): if login ok, p will be added to Pupils list
- Logout(Pupil p): if logout ok, p will be removed out of Pupil list
-> 2 methods have a same potential issue is "cannot modify list while modifying" because there are a lot of Pupil login to system and logout at the same time. While adding a pupil the other are being removed from Pupils list -> exception throws
I tried to use lock to lock a list while modifying (insert/remove) but is it a good way to do? Do you have some better idea?
The last method is Claim(Book b)
Administrator put some books in the GUI and all the logged Pupils can see these books. They can claim any book they want. The fastest Pupil claimed is will own that book. So how can we know the fastest claimer? While updating a data row. At the same time there are many books are claimed by many pupils. But only one fastest pupil can own a particular book after claiming successfully
Do you have a solution for this? This solution just like you put a command to buy stocks. The fastest guy will own the stocks
Remember that there are many pupils will do the same thing at the same time. So we have to make sure that system works properly and exactly
Thanks you in advance,
Best regards
The issue I see here is that your design assumes that the application is always on, and that the lists are the absolute truth about who has what book. What happens when the server is reset? What if your library gets big enough to need a second server to the application?
You need to preserve your lists in a database or some other sort of persistent medium. Holding the lists in memory will give you a read/write buffer, but the lists must be populated from the persistence layer of your library database.
using System;
using System.Collections.Concurrent;
namespace TestArea
{
public class Pupil
{
public Guid Id { get; set; }
public string Name { get; set; }
public string UserName { get; set; }
}
public class Book
{
//Supports having more than one ISBN in the library... We may have more than one To Kill a Mockingbird in our school library
public Guid Id { get; set; }
public string ISBN { get; set; }
}
public class SchoolLibrary
{
private ConcurrentDictionary<Guid, Pupil> Pupils { get; set; }
private ConcurrentDictionary<Guid, Book> Books{ get; set; }
private ConcurrentDictionary<Guid, Guid> CheckOuts { get; set; }
public Pupil Login(string userName, string password)
{
//Call repository to authenticate pupil into library system
//Mocked return assuming password check success
var id = Guid.NewGuid();
return Pupils.GetOrAdd(id, (i) =>
{
//Replace with function to get student info
return new Pupil
{
Id = i,
Name = "Bac Clunky",
UserName = userName
};
});
}
public bool CheckOut(Guid pupilId, Guid bookId)
{
//If book exists
if (Books.ContainsKey(bookId))
{
Guid currentOwner;
//...is not currently checked out by anyone
if (CheckOuts.TryAdd(bookId, pupilId))
{
return true; //book is now checked out
}
if (CheckOuts.TryGetValue(bookId, out currentOwner))
{
return currentOwner == pupilId; //returns true if pupil already has the book, false if another student has it
}
}
return false; //all other cases fail to check out book
}
}
}

Saving an Item in EF with existing childrens

Im having some problems saving an object (FeatureType) that have a 1-M relationship with Section.
public class FeatureType
{
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("SectionId")]
public Section Section { get; set; }
public virtual List<ItemType> ItemTypes { set; get; }
}
public class Section
{
public int Id { get; set; }
public string Name { get; set; }
public int Order { get; set; }
public virtual List<FeatureType> Features { get; set; }
}
If The ItemTypes are new i have no problem and the insert is done correctly.
But if i want to add some existing ItemTypes im getting this Error:
An entity object cannot be referenced by multiple instances of
IEntityChangeTracker.
I have been reading about this problem but i havent found a way to solve it, and it might be because of how its designed my application.
Whem im mappinig from my viewModel to my Model, im getting the section ID and getting the section Object from my SectionRepository as this:
private Section GetSection()
{
var section = _sectionRepository.GetSection(SectionId);
return section;
}
And this is what is giving me the problem, as the section is now been tracked by the SectionRepository that have its own context.
How can i solve this? I have tried just creating a new section with the existing ID but it just create me an empty object.
private Section GetSection()
{
var section = new Section{Id=SectionId};
return section;
}
UPDATE
To save my entity i just use :
_repository.Create(featureType.ToModel());
public FeatureType ToModel()
{
var ft = new FeatureType
{
Name = Name,
ControlType = (ControlType)ControlType,
Order = Order,
Required = Required,
RequiredText = RequiredText,
ItemTypes = GetItemTypes().ToList(),
Section = GetSection(),
};
return ft;
}
UPDATE 2: This is how i have my repositories, i wouldn't like to manage any EF in my controller but with some kind of repository or service.
public class EFBaseRepository
{
protected MyContext Db = new MyContext();
public void Dispose(bool disposing)
{
Db.Dispose();
}
}
public class EFFeatureTypeRepository : EFBaseRepository, IFeatureTypeRepository
{
public IQueryable<FeatureType> GetFeatureTypes
{
get { return Db.FeatureTypes.Include("Section").Include("ItemTypes"); }
}
public Message Create(FeatureType feature)
{
try
{
Db.FeatureTypes.Add(feature);
Db.SaveChanges();
return new Message();
}
catch (Exception e)
{
throw;
// return new Message(e, string.Format("Error Creating {0}", feature.GetType()));
}
}
//..Other Methods
}
You say that the SectionRepository has its own context. That is going to cause you problems. The repositories should share a context. The context is a combination of the unit of work and repository patterns. You need to separate the two patterns:
How to migrate towards unit-of-work and repository pattern
EDIT
You can avoid having the DbContext in the Controller by implementing your own Unit Of Work pattern.
public interface IUnitOfWork : IDisposable
{
ISectionRepository SectionRepository {get;}
//etc
int Save();
}
then in your controller:
public ActionResult Create(FeatureTypeCreate featureType)
{
_Uow.SectionRepository.Create(featureType.ToModel());
_Uow.Save(); //Saving is the responsibility of the Unit Of Work
//not the Repository
}
More references:
Implementing the Repository and Unit of Work
Repository and Unit of Work in Entity Framework
John Papa's original source code
Simply, the error you're getting means that the entities were returned from a different instance of your DbContext than from which they are now trying to be saved. Make sure that you're not doing something like using two different usings around your repository and that your repository always makes use of the same DbContext per instantiation.

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"?

Categories