I've been looking managing Root Aggregate state/life-cycle and found some content about the benefits of using Explicit State Modeling or Explicit Modeling over State Pattern, it's much cleaner and I like how I can let explicit concepts of my domain handle their own behavior.
One of the things I read was this article that is influenced by Chapter 16 in "Patterns, Principles, and Practices of Domain-Driven Design - Scott Millett with Nick Tune" book (here's a code sample for the full example).
The problem is the idea is described very briefly and there is not much content around it and that appeared when I started to implement it and given that I am new to DDD, the concepts started to overlap, and here are some of the questions that I am hoping more experienced engineers in DDD would help or at least someone has interpreted the text better than me.
Following the article's example, how would I retrieve a list of all doors (that are both open and closed), what Domain Entity would this result-set map to?
If all the explicit states models are entities/aggregates, what would be the root aggregate?
would it be normal that there is no reference between Root Aggregate and those explicitly modeled entities?
And if the Aggregate Root (let's say a generic Door entity) returns an explicit state entity, how would the repository save it without exposing the state of the entity or aggregate?
Or are all these explicit entities root of their own aggregate?
I am not expecting to get all the above answered, I am just sharing the thoughts that am stuck at, so you are able to see where I am standing, as there is too much ambiguity for me to share code but I hope the snippets from the article and the book can help.
A git repository or a sample project addressing how would other DDD components with Explicit modeling would be really helpful (I have checked a million repositories but 90% with no luck).
Note: I am not using CQRS
Example from Medium Article:
interface ClosableDoor
{
public function close();
}
// Explicit State. (third attempt)
class CloseDoorService()
{
// inject dependencies
public function execute($doorId)
{
$door = $this->doorRepository->findClosableOfId($doorId);
if (!$door) {
throw new ClosableDoorNotFound();
}
$door = $door->close();
$this->doorRepository->persist($door);
}
}
Example from the book:
// these entities collectively replace the OnlineTakeawayOrder entity (that used the state pattern)
public class InKitchenOnlineTakeawayOrder
{
public InKitchenOnlineTakeawayOrder(Guid id, Address address)
{
...
this.Id = id;
this.Address = address;
}
public Guid Id { get; private set; }
public Address Address { get; private set; }
// only contains methods it actually implements
// returns new state so that clients have to be aware of it
public InOvenOnlineTakeawayOrder Cook()
{
...
return new InOvenOnlineTakeawayOrder(this.Id, this.Address);
}
}
public class InOvenOnlineTakeawayOrder
{
public InOvenOnlineTakeawayOrder(Guid id, Address address)
{
...
this.Id = id;
this.Address = address;
}
public Guid Id { get; private set; }
public Address Address { get; private set; }
public CookedOnlineTakeawayOrder TakeOutOfOven()
{
...
return new CookedOnlineTakeawayOrder(this.Id, this.Address);
}
}
Note: I am not using CQRS
I think this is the biggest challenge you have.
Retrieving explicitly modelled entities for the purpose of the use case being implemented would not cause such a headache if you were not also trying to use them for queries that may not be constrained to an explicit model designed for a specific use case.
I use Entity Framework which supports "table-splitting" that could help in this situation. Using this, many entities can be mapped to the same table but each can deal with a subset of the fields in the table and have dedicated behaviour.
// Used for general queries
class Door
{
public Guid Id { get; private set; }
public State State { get; private set; }
// other props that user may want included in query but are not
// relevant to opening or closing a door
public Color Color { get; private set; }
public Dimensions Dimensions { get; private set; }
public List<Fixing> Fixings { get; private set; }
}
class DoorRepository
{
List<Door> GetDoors()
{
return _context.Doors;
}
}
// Used for Open Door use case
class ClosedDoor
{
public Guid Id { get; private set; }
public State State { get; private set; }
public void Open()
{
State = State.Open;
}
}
class ClosedDoorRepository
{
List<ClosedDoor> GetClosedDoors()
{
return _context.ClosedDoors.Where(d => d.State == State.Closed);
}
}
// Used for Close Door use case
class OpenDoor
{
public Guid Id { get; private set; }
public State State { get; private set; }
public void Close()
{
State = State.Closed;
}
}
class OpenDoorRepository
{
List<OpenDoor> GetOpenDoors()
{
return _context.OpenDoors.Where(d => d.State == State.Open);
}
}
What are your methods to deal with the communication of an admin panel with a domain in the case of changing values of properties of an entity without breaking the encapsulation?
public class Book : Entity {
public Book(string title, string author, string description, decimal price, short publicationYear) {
Title = title;
Author = author;
Description = description;
Price = price;
PublicationYear = publicationYear;
}
public string Title { get; private set; }
public string Author { get; private set; }
public string Description { get; private set; }
public decimal Price { get; private set; }
public short PublicationYear { get; private set; }
}
The only way to not break encapsulation is to include some parts of the presentation logic into the object itself. Not the details, mind you, but the parts which are highly coupled to this object.
I would do something like this (pseudo-code):
public class Book {
public Book(...) {
...
}
public InputComponent<Book> createAdminView() {
return new FormGroup<Book>(
new TextInput(title),
new TextInput(author),
...);
}
}
This way there is no need to publish any of the internal data fields of the object, nobody needs to know how to book looks like, and all changes related to the object will be localized.
In fact, I've been doing this for a couple for years now, and this design results in much easier to maintain code. Have a look at my presentation about Object-Oriented Domain-Driven Design to find out more: https://speakerdeck.com/robertbraeutigam/object-oriented-domain-driven-design
So, I'm building a system for managing contacts. My contact domain model has quite a few string properties, as well as booleans. In the spirit of keeping behavior inside of the domain models, I've gone down the path of creating "update methods." I'm starting to feel like it's getting a bit burdensome. In the past, CRUD apps would just have a single update method and it would set all of the properties in one shot.
Am I on the right path? I'm concerned about having 10 - 15 update methods on my domain service and domain entities.
FYI, the example given is a bit contrived, so imagine a model with lots of string and boolean properties.
// Application Layer Stuff
public class UpdateContactCommand
{
public UpdateNamePredicate UpdateName { get; set; }
public UpdatePhonePredicate UpdatePhone { get; set; }
public int ContactId { get; set; }
}
public class UpdateNamePredicate
{
public string NewFirstName { get; set; }
public string NewLastName { get; set; }
}
public class UpdatePhonePredicate
{
public string NewPHone { get; set; }
}
public class UpdateContactResponse
{
public bool Success { get; set; }
public string Message { get; set; }
}
public interface IWcfService
{
UpdateContactResponse UpdateContact(UpdateContactCommand updateContactCommand);
}
public class WcfService : IWcfService
{
private readonly IContactService _contactService;
public WcfService(IContactService contactService)
{
_contactService = contactService;
}
public UpdateContactResponse UpdateContact(UpdateContactCommand updateContactCommand)
{
if (updateContactCommand.UpdateName != null)
{
_contactService.UpdateName(updateContactCommand.ContactId, updateContactCommand.UpdateName.NewFirstName,
updateContactCommand.UpdateName.NewLastName);
}
if (updateContactCommand.UpdatePhone != null)
{
_contactService.UpdatePhone(updateContactCommand.ContactId, updateContactCommand.UpdatePhone.NewPHone);
}
return new UpdateContactResponse();
}
}
// Domain Layer
public interface IContactService
{
// There are lots more of these
void UpdateName(int contactId, string newFirstName, string newLastName);
void UpdatePhone(int contactId, string newPhone);
}
public class ContactService : IContactService
{
private readonly IContactRepository _contactRepository;
public ContactService(IContactRepository contactRepository)
{
_contactRepository = contactRepository;
}
public void UpdateName(int contactId, string newFirstName, string newLastName)
{
var contact = _contactRepository.GetById(contactId);
contact.SetName(newFirstName, newLastName);
_contactRepository.Commit();
}
public void UpdatePhone(int contactId, string newPhone)
{
var contact = _contactRepository.GetById(contactId);
contact.SetPhone(newPhone);
_contactRepository.Commit();
}
}
public interface IContact
{
int Id { get; set; }
// There are lots more of these
void SetName(string newFirstName, string newLastName);
void SetPhone(string newPhone);
}
public class Contact : IContact
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Phone { get; set; }
public void SetName(string newFirstName, string newLastName)
{
FirstName = newFirstName;
LastName = newLastName;
}
public void SetPhone(string newPhone)
{
Phone = newPhone;
}
}
public interface IContactRepository
{
IContact GetById(int id);
void Commit();
}
public class ContactRepository : IContactRepository
{
public IContact GetById(int id)
{
// Not important
throw new NotImplementedException();
}
public void Commit()
{
// Not important
throw new NotImplementedException();
}
}
First of all, not all applications lend themselves well to a DDD approach. If you say your application could pretty much have been implemented in a CRUDish way before, chances are it's still CRUD now. Don't try to apply DDD on any app because it's the shiny new thing.
That being said, you don't just write "update methods" for the fun of it. They have to reflect the domain tasks your user wants to perform. Why does the user want to update a Contact ? Has the contact moved or just changed phone number ? Changed marital status and name ? Has the point of contact in a company been taken over by another employee ?
Usually, you won't have tons of update methods for a given entity. There's always a way to group changes in operations that are meaningful for the domain. Good ways to force yourself to do it are :
Think about the maximum number of form fields you can reasonably display to the user. Couldn't you split that complex UI into smaller, more meaningful screens ? From there you have to start reasoning (preferably with the help of a domain expert) about the tasks these should reflect.
Make your entity fields immutable from the outside. Thus you'll have to think harder about their true nature -- what should be in the constructor ? what should some other manipulation methods be ?
Breeze doesn't expand TPH entities correctly.
When using expand in breeze if you are using TPH expand will only work for the first entity, the others properties will be null. If I change the entity not to use inheritances it works fine. I've also tested returning each entity separately in an expand query that also worked fine.
//client side code
var getResidentById = function (id, obs) {
var query = EntityQuery.from('Residents')
.where('id', '==', id)
.expand('user, currentUnit, leases, leases.unit, leases.leaseStatus');
return manager.executeQuery(query).then(function (data) {
if (obs) {
obs(data.results[0])
}
}, queryFailed);
};
//Controler Endpoint
[HttpGet]
public IQueryable<Resident>
{
return _context.Context.UserDetails.OfType<Resident>();
}
//Model
public class UserDetail : EntityBase<int>, IArchivable, IHasPhoto, IDeactivatableEntity, IUpdatable
{
public bool IsArchived { get; set; }
public int LastUpdatedById { get; set; }
public UserProfile LastUpdatedBy { get; set; }
public DateTimeOffset LastUpdatedDate { get; set; }
public string PhotoUri { get; set; }
public bool IsInactive { get; set; }
}
public abstract class UserBelongsToApartmentComplex : UserDetail, IBelongsToApartmentComplex
{
public int ApartmentComplexId { get; set; }
public virtual ApartmentComplex ApartmentComplex { get; set; }
public virtual bool IsInSameComplexAs(IRelatedToApartmentComplex otherEntity)
{
return ApartmentComplexId == otherEntity.ApartmentComplexId;
}
}
public class Staff : UserBelongsToApartmentComplex
{
public string Title { get; set; }
}
public class Admin : UserDetail
{
public string AccessLevel { get; set; }
}
public class Resident : UserBelongsToApartmentComplex
{
public string Pets { get; set; }
public bool HasInsurance { get; set; }
public virtual IList<Lease> Leases { get; set; }
public int? CurrentUnitId { get; set; }
public virtual Unit CurrentUnit { get; set; }
public Resident()
{
Leases = new List<Lease>();
}
}
//response data from sever from endpoint public IQueryable Residents()
[{"$id":"1","$type":"RadiusBlue.Core.Models.Resident, RadiusBlue.Core","Pets":"Sadie, a westie","HasInsurance":false,"Leases":[{"$id":"2","$type":"RadiusBlue.Core.Models.Lease, RadiusBlue.Core","Start":"2012-05-23T00:00:00.000","End":"2013-05-23T00:00:00.000","UnitId":2,"Unit":{"$id":"3","$type":"RadiusBlue.Core.Models.Unit, RadiusBlue.Core","Building":"B","Floor":2,"ModelName":"Tera","RentAmount":2500.00,"NumberOfBeds":1,"NumberOfBaths":3,"UnitName":"102A","IsInactive":true,"Inhabitants":[],"ApartmentComplexId":1,"ApartmentComplex":{"$id":"4","$type":"RadiusBlue.Core.Models.ApartmentComplex, RadiusBlue.Core","Name":"The Stratford","StreetAddress":"100 S Park Ave","City":"Winter Park","StateId":10,"ZipCode":"32792","PropertyManagementCompanyId":1,"IsInactive":false,"TimeZoneId":"Eastern Standard Time","TimeZone":{"$id":"5","$type":"System.TimeZoneInfo, mscorlib","Id":"Eastern Standard Time","DisplayName":"(UTC-05:00) Eastern Time (US & Canada)","StandardName":"Eastern Standard Time","DaylightName":"Eastern Daylight Time","BaseUtcOffset":"-PT5H","AdjustmentRules":[{"$id":"6","$type":"System.TimeZoneInfo+AdjustmentRule, mscorlib","DateStart":"0001-01-01T00:00:00.000","DateEnd":"2006-12-31T00:00:00.000","DaylightDelta":"PT1H","DaylightTransitionStart":{"$id":"7","$type":"System.TimeZoneInfo+TransitionTime, mscorlib","TimeOfDay":"0001-01-01T02:00:00.000","Month":4,"Week":1,"Day":1,"DayOfWeek":"Sunday","IsFixedDateRule":false},"DaylightTransitionEnd":{"$id":"8","$type":"System.TimeZoneInfo+TransitionTime, mscorlib","TimeOfDay":"0001-01-01T02:00:00.000","Month":10,"Week":5,"Day":1,"DayOfWeek":"Sunday","IsFixedDateRule":false}},{"$id":"9","$type":"System.TimeZoneInfo+AdjustmentRule, mscorlib","DateStart":"2007-01-01T00:00:00.000","DateEnd":"9999-12-31T00:00:00.000","DaylightDelta":"PT1H","DaylightTransitionStart":{"$id":"10","$type":"System.TimeZoneInfo+TransitionTime, mscorlib","TimeOfDay":"0001-01-01T02:00:00.000","Month":3,"Week":2,"Day":1,"DayOfWeek":"Sunday","IsFixedDateRule":false},"DaylightTransitionEnd":{"$id":"11","$type":"System.TimeZoneInfo+TransitionTime, mscorlib","TimeOfDay":"0001-01-01T02:00:00.000","Month":11,"Week":1,"Day":1,"DayOfWeek":"Sunday","IsFixedDateRule":false}}],"SupportsDaylightSavingTime":true},"Users":[{"$ref":"1"}],"Groups":[],"IsArchived":false,"ApartmentComplexId":1,"Id":1},"Id":2},"ResidentId":3,"Resident":{"$ref":"1"},"LeaseStatusId":4,"LeaseStatus":{"$id":"12","$type":"RadiusBlue.Core.Models.LeaseStatus, RadiusBlue.Core","Description":"Lost","Id":4},"Id":1},{"$id":"13","$type":"RadiusBlue.Core.Models.Lease, RadiusBlue.Core","Start":"2013-05-24T00:00:00.000","End":"2014-05-24T00:00:00.000","UnitId":1,"Unit":{"$id":"14","$type":"RadiusBlue.Core.Models.Unit, RadiusBlue.Core","Building":"A","Floor":2,"ModelName":"Aqua","RentAmount":2000.00,"NumberOfBeds":2,"NumberOfBaths":1,"UnitName":"101A","IsInactive":true,"Inhabitants":[{"$ref":"1"}],"ApartmentComplexId":1,"ApartmentComplex":{"$ref":"4"},"Id":1},"ResidentId":3,"Resident":{"$ref":"1"},"LeaseStatusId":1,"LeaseStatus":{"$id":"15","$type":"RadiusBlue.Core.Models.LeaseStatus, RadiusBlue.Core","Description":"Active","Id":1},"Id":2}],"CurrentUnitId":1,"CurrentUnit":{"$ref":"14"},"ApartmentComplexId":1,"ApartmentComplex":{"$ref":"4"},"Id":3,"User":{"$id":"16","$type":"RadiusBlue.Core.Models.UserProfile, RadiusBlue.Core","UserName":"vjiawon#gmail.com","FirstName":"Vishal","LastName":"Jiawon","Age":27,"PhoneNumber":"123 456 7890","IsInactive":false,"UserDetail":{"$ref":"1"},"GroupMembers":[],"MaintenanceRequests":[],"Id":3},"IsArchived":false,"LastUpdatedById":1,"LastUpdatedDate":"0001-01-01T00:00:00.000+00:00","IsInactive":false,"CreatedById":1,"CreatedDate":"0001-01-01T00:00:00.000+00:00"}]
I do not doubt that there is a bug in BreezeJS somewhere.
I can report that, at least as of v.1.3.4, Breeze can expand multiple navigation properties of a TPH class ... and not just on the first entity returned.
I just modified the "can navigate to AccountType eagerly loaded with expand" test in inheritanceTests.js in DocCode so that (a) it also expands the Status navigation and (b) the tests are performed on the 3rd entity returned rather than the 1st.
The query is something like this:
var em = newEm(); // clean, empty EntityManager
return EntityQuery.from('bankRootTPHs').take(3)
.expand('AccountType, Status'))
.using(em).execute().then(success).fail(handleFail);
...
function success(data) {
var entity = data.results[data.results.length-1]; // get the last one (the 3rd)
var type = data.query.entityType.shortName;
if (!entity) {
ok(false, "a query failed to return a single " + type);
}
// more tests
// I just set a breakpoint and inspected
// entity.accountType() and entity.status()
// Both returned the expected related entities
}
I see that both the related AccountType and the related Status are available from the entity.
So something else is wrong.
Questions about your Example
First I am compelled to observe that you have a lot of expands. I count 5 related entities. That can hurt performance. I know we're not talking about that but I'm calling it out.
Second, the super class UserDetail is concrete but the intermediate derived class UserBelongsToApartmentComplex is abstract. You have inheritance class hierarchies that go concrete/abstract/concrete. The queried type, Residents is one such class. And a class at every level maps to the "UserDetail" table, yes?
I'm pretty sure we didn't test for that scenario ... which is pretty uncommon. I wasn't even sure that worked! For now I have to take your word for it that EF allows such a construct.
It would seem that BreezeJS is confused about it. We'll take a look.
I see these types of model is many samples online.
public class User
{
public long Id { get; set; }
public string Name{ get; set; }
public virtual ICollection<Product> Products { get; set; }
}
Is it considered a good practice to instantiate a collection in the constructor like the code below? If so what are the reasons? How about objects in the model?
public class User
{
public User()
{
Products = new List<Product>();
}
public long Id { get; set; }
public string Name{ get; set; }
public virtual ICollection<Product> Products { get; set; }
}
Well, I would say it depends on the situation, but Products in this case would be filled from the database, via a repository, so most probably ORM of some sort, so no initialization to new List would be needed in the constructor. The meaning of null for Products is indicative that the list isn't loaded yet. On the other hand, let's say that your object must have this collection initialized. For simple objects DDD says constructors are perfectly fine to to these things, but in case of complex objects, move the construction to the Factory.