I was wondering if how my presentation layer is structured could be a lead to design my aggregate roots.
Lets have an entity ProjectEntity and its related entity ProjectMemberEntity (1:M)
The page is structured as follows:
The top of the page is a form for ProjectEntity
Underneath the form is a grid that shows a list of ProjectMemberEntity.
If a new ProjectMember will be added, the user have to go to this page and click on the button "add new member" which is located in the header of the grid.also edit and delete has the same analogy.
I'm wondering if this behavior/'page structure' could be a hint for a aggregate root(projectentity)
That's a hint for sure. But no more.
Better way to clarify kind of that entity relationship is to ask domain expert:
does project member makes any sense without project?
can member participate in multiple projects?
If those are answered positively, it's highly likely that You should model project member as an aggregate root itself. Otherwise - demote it as an entity that cannot live w/o a project.
Here is some code that might give You some ideas:
public class Project:Root{
private List _members;
public IEnumerable<Member> Members{get {return _members;}}
public string Name{get;private set;}
public bool IsFinished{get;private set;}
public bool FinishedOn{get;private set;}
public Project(string projectName){
_members=new List<Member>();
Name=projectName;
}
public Member AssignMember(string memberName){
var member=new Member(memberName);
_members.Add(member);
return member;
}
public void UnassignMember(string memberName){
var member=_members.First(m=>m.Name==memberName);
if(!member.HasCompletedAllTasks())
throw new Exception
("Cannot unassign member with incompleted tasks!");
_members.Remove(member);
}
public void AssignTaskToMember(string taskName, string memberName){
var member=_members.First(m=>m.Name==memberName);
member.AssignTask(taskName);
}
public void MemberHasCompletedTask(Member member, Task task){
EnsureListContains(_members,member);
EnsureListContains(member.Tasks,task);
task.MarkAsCompleted();
}
public void FinishProject(){
if(_members.Any(m=>!m.HasCompletedAllTasks()))
throw new Exception
("Can't finish project before members have completed all their tasks.");
IsFinished=true;
FinishedOn=DateTime.Now;
}
private void EnsureListContains<T>(IList<T> lst, T itm){
if(!lst.Contains(itm)) throw new Exception();
}
}
public class Member:Entity{
public string Name{get;private set;}
private List<Task> _tasks;
public IEnumerable<Task> Tasks{get{return _tasks;}}
internal Member(string memberName){
Name=name;
_tasks=new List<Task>();
}
internal void AssignTask(string taskName){
_tasks.Add(new Task(taskName));
}
public bool HasCompletedAllTasks(){
return _tasks.All(t=>t.IsCompleted);
}
public Task GetNextAssignedTask(){
return _tasks.Where(t=>!t.IsCompleted)
.OrderBy(t=>t.AssignedOn).First();
}
}
public class Task:Entity{
public string Name{get; private set;}
public bool IsCompleted{get; private set;}
public DateTime CompletedOn{get; private set;}
public DateTime AssignedOn{get; private set;}
internal Task(string name){
Name=name;
AssignedOn=DateTime.Now;
}
internal void MarkAsCompleted(){
if(IsCompleted) throw new Exception
("Task is already completed!");
IsCompleted=true;
CompletedOn=DateTime.Now;
}
}
public class App{
public static void Main(){
var project=new Project
("Question: Aggregate root design and presentation layer");
var me=project.AssignMember("Arnis Lapsa");
project.AssignTaskToMember("Try to help user137348","Lapsa");
var currentTask=me.GetNextAssignedTask();
//SpamStackOverflow();
project.MemberHasCompletedTask(me,currentTask);
if(me.HasCompletedAllTasks()) project.Finish();
else throw new Exception("Enough for today...");
}
}
Keep in mind that I got little knowledge of what Your business is about. This is just an improvisation. :)
When it comes to DDD, make sure you don't get analysis paralysis when trying to design your domain and aggregates. It happened to me. My project got literally STOPPED for a whole month because i wasn't enable to get my aggregates straight. And i am talking about a simple 3 database tables situation. User, Address and UserProfile.
The thing with DDD is that there is no such a thing like DONE THE RIGHT WAY. If you post the same question here with an interval of 3 months from each other, you will always get the "experts" giving you completely different answers in each question. Amis L. was kind enough to give you a solid simple example. Most people would copy and paste from Eric's books.
Do whatever floats your boat. At the end of the day, no matter how handcrafted your domain is, it's never RIGHT to the community. Just chill and enjoy coding.
Related
We have a graph extension that manages the tab we added to a screen. The tab displays data from our own DAC. We do not use a DAC extension, not going to go into the reasons right now.
When a user opens the main screen I want to create a record of our data if it does not exist, with some defaulting business logic.
I added a RowSelected event handler on the main DAC and it fires as I expect it to. When I add the code to create our missing record in the event handler Acuminator gives me error "PX1044 Changes to PXCache cannot be performed in event handlers."
I understand the error that Acuminator is raising, but I'm not sure where else to create our record. I cannot remember a section of the university specifically addressing this scenario.
Can anyone tell me how I would handle this scenario? And if possible, point me at the learning material that covers this scenario for broader information.
Unfortunatly there are only a few OK ways to do this and make Acuminator happy. You will risk data inconsistency.
https://github.com/Acumatica/Acuminator/blob/master/docs/diagnostics/PX1044.md
I would reccomend putting your data insertion code in Row Updated and hope the user updates something
I got a reply from Acumatica support, you can do this in a view select delegate.
Here's some example code, I haven't tested it though:
public class InventoryItemMaintExtension : PXGraphExtension<InventoryItemMaint>
{
public SelectFrom<MyCustomTable>.
Where<MyCustomTable.inventoryID.IsEqual<
InventoryItem.inventoryID.FromCurrent>>.
View AdditionalItemData;
public static bool IsActive() { return true; }
public IEnumerable additionalItemData()
{
var data = AdditionalItemData.SelectSingle();
if (data is null)
{
data = new MyCustomTable();
AdditionalItemData.Insert(data);
}
return AdditionalItemData.Select();
}
}
[PXCacheName("Additional Item Data")]
[Serializable]
public class MyCustomTable : IBqlTable
{
#region CashAccountID
[Inventory]
[PXDefault]
public virtual int? InventoryID { get; set; }
public abstract class inventoryID : PX.Data.BQL.BqlInt.Field<inventoryID> { }
#endregion
#region Required
[PXDBBool()]
[PXDefault(false)]
[PXUIField(DisplayName = "Required")]
public virtual bool? Required { get; set; }
public abstract class required : PX.Data.BQL.BqlBool.Field<required> { }
#endregion
}
I want to clear one doubt. I am creating my own service class to manipulate data. Should i create this service instance at class level(in MVCPortlet) or create new instance in my processAction/doView method.
Is there is any issue of thread safety while using instance level.
Fg:
public class MvcCycle extends MVCPortlet {
int counter;
LdapService ldapservice;
#Override
public void init() throws PortletException {
counter=0;
ldapservice = new LdapService(); // Option 1
super.init();
}
#Override
public void doView(RenderRequest renderRequest, RenderResponse renderResponse) throws IOException, PortletException {
// TODO Auto-generated method stub
System.out.println("Counter hits "+ ++counter);
//
LdapService ldapservice = new LdapService(); // Option 2
ldapservice.authUser(request.getParameter("email"));
// -- some code--
super.doView(renderRequest, renderResponse);
}
#Override
public void destroy() {
System.out.println("Last counter "+counter);
counter=0;
super.destroy();
}
}
class LdapService{
public boolean authUser(String email){
if(//logic to authenticate user){
return true;
}else{
return false;
}
}
}
Here, my ldapservice initiate only once. So when multiple hits come. which code is beneficial on this time either Option1 or Option 2.
Hope i have cleared my problem.
Following up on your comment: in general: it depends. We don't have enough information to give you blank architectural recommendations.
In the context of Liferay it could make sense to utilize service builder, even if you don't have database interaction. Just create an empty entity, you'll have a service with no persistence.
<entity name="MyService" local="true"/>
That's it. Now implement MyServiceLocalServiceImpl added you're done
Edit, after your clarification: it doesn't make a difference. I'd still recommend to not instantiate at all in your portlet, because that makes your portlet dependent on the service implementation. E.g. I still go with service builder.
You're also asking about thread safety and that depends on your implementation as well. Your sample code is thread safe, but the actual code might not be. Your judgement.
I'm confused with how to synchronise data to the query database.
Let's say I have an aggregate: CreditAccount and some commands may produce CreditAccountBalanceChangedEvent:
public class CreditAccount extends AbstractAnnotatedAggregateRoot<Long> {
#AggregateIdentifier
private Long id;
private int balance;
private DateRange effectiveDateRange;
#CommandHandler
public CreditAccount(CreateCreditAccountCommand command) {
apply(new CreditAccountCreatedEvent(command.getAccountId(),
command.getEffectiveDateRange()));
apply(new CreditAccountBalanceChangedEvent(command.getAccountId(),
command.getAmount()));
}
#EventHandler
private void on(CreditAccountCreatedEvent event) {
this.id = event.getAccountId();
this.effectiveDateRange = event.getEffectiveDateRange();
}
#EventHandler
private void on(CreditAccountBalanceChangedEvent event) {
//notice this line, some domain logic here
this.balance = add(this.balance, event.getAmount());
}
private int add(int current, int amount) {
return current + amount;
}
}
public class CreditAccountBalanceChangedEvent {
private final long accountId;
private final int amount;
//omitted constructors and getters
}
And everything works fine on the command handler side. And I set off to the query side but I find I'm writing some duplicate domain logic here:
#Transactional
#Slf4j
public class CreditAccountEventHandler {
private CreditAccountReadModelStore creditAccountReadModelStore;
#EventHandler
public void handle(CreditAccountCreatedEvent event) {
log.info("Received " + event);
creditAccountReadModelStore.store(accountDevriveFrom(event));
}
#EventHandler
public void handle(CreditAccountBalanceChangedEvent event) {
log.info("Received " + event);
final CreditAccountReadModel account = creditAccountReadModelStore
.findBy(event.getAccountId());
//notice this line, some domain logic here
account.setBalance(account.getBalance() + event.getAmount());
creditAccountReadModelStore.store(account);
}
//omitted setters and private methods
}
As you may notice, I wrote balance calculation code on both command and query side. My question is that is this inevitable in some situations or I write domain logic in wrong place?
As my study so far, events represent something have occured, so no business logic in them, they're just data holder(but reveal users's intent). So should I add a 'balance' field to CreditAccountBalanceChangedEvent and move balance calculation code to command handler method?
public class CreditAccount extends AbstractAnnotatedAggregateRoot<Long> {
//omitted fields
#CommandHandler
public CreditAccount(CreateCreditAccountCommand command) {
apply(new CreditAccountCreatedEvent(command.getAccountId(),
command.getEffectiveDateRange()));
apply(new CreditAccountBalanceChangedEvent(command.getAccountId(),
command.getAmount(), add(this.balance, command.getAmount())));
}
#EventHandler
private void on(CreditAccountBalanceChangedEvent event) {
//notice this line, some domain logic here
//event.getAmount() is no use here, just for auditing?
this.balance = event.getBalance();
}
}
In this case, I can remove balance calculation on the query side by using event.getBalance().
Sorry for a screen full question, any idea is appreciate.
I see two options.
One is for the command to contain the change in balance, the command handler to calculate the new balance, and the event to contain the new balance. If nothing is recalculated in the event handler, it ensures that if the business rules change in the future, they do not affect your object's history when when it is reconstituted from the events.
An alternative would be to place the business rules in a separate class that is called from both the command handler and the event handler to avoid duplication, and then to version those business rules -- via subclassing for example. So you could have an abstract class called CalculateBalanceRule with a subclass of CalculateBalanceRuleVersion1 that is initially referenced by both. If the rule changes, you create CalculateBalanceRuleVersion2, change your command handler to reference it, but keep the reference to Version1 in your event handler, so that it will always replay the rules it did originally.
The second approach is definitely more maintenance, but can answer HOW something change, not simply WHAT changed, if that's something that's important to your business.
Edit: A third option is for the event to only contain the new balance like in the first option, but to version the events. So you have BalanceChangedEvent, BalanceChangedEvent_v2, and so on. This is the direction I could take, as I don't really care to keep a history of how things changed, but I do need to account for the possibility that the events themselves might take on additional members or rename its members. Logic is then needed to determine which event version to use to reconstitute the object at each step.
I have these entities
User
Task - every task must have a user assigned
Project - a task can be assigned to a project but it's not necessary, user must be assigned as a project creator
TaskStatus - every task must have a status assigned
and I designed the aggregate roots as follows
Aggregate roots
User with entities:Task, Task status
Project
Am I going in right direction ?
Yeah, sounds quite right. This is my improvisation in c#:
//aggregate root
public class User{/*first name, last name, contacts, etc.*/}
//aggregate root
public class Project{
public IList Tasks{get; private set;}
public User CreatedBy{get; private set;}
public Project(User createdBy){
CreatedBy=createdBy;
}
public RegisterTask(string taskDescription, User assignedTo){
Tasks.Add(new Task(taskDescription, assignedTo));
}
public StartWorkOn(Task task){
if(!Tasks.Contains(task))
throw new Exception("Task is not registered");
task.StartWork();
}
}
//entity
public class Task{
public User AssignedTo{get;private set;}
public string Description{get; private set;}
public TaskStatus Status{get; private set;}
internal Task(string description, User assignedTo){
AssignedTo=assignedTo;
Description=description;
Status=TaskStatus.Pending;
}
internal void StartWork(){
if(Status!=TaskStatus.Pending)
throw new Exception("Can't start what's ongoing");
Status=TaskStatus.InProgress;
}
internal void Finish(){
if(Status!=TaskStatus.InProgress)
throw new Exception("Can't finish what has not started");
Status=TaskStatus.Finished;
}
}
//value object
public enum TaskStatus{ Pending, InProgress, Finished }
var me=new User();
_users.Save(me);
var you=new User();
_users.Save(you);
var project=new Project(me);
project.RegisterTask("Have a nice day!", you);
project.StartWorkOn(project.Tasks.First());
_projects.Save(project);
Keep in mind - that's just a sketch. Don't take it too seriously.
I'm looking for recommendations on how to approach the following design problem (using a fictitious example based on stackoverflow). I'd trying to avoid an anemic domain model and seek general "best-practice" advice for this type of case.
Scenario:
Suppose a new feature is being developed for stackoverflow that sends an email notification to a question's owner whenever his/her question receives 10 upvotes.
The domain object model is something like this:
public class Question
{
string Question { get; set; }
IList<Votes> Upvotes { get; set; }
User Owner { get; set; }
public void AddUpvote(Vote upvote)
{
Upvotes.Add(upvote);
}
}
Potential Implementations:
Change AddUpvote() to take an IEmailerService parameter and perform the logic within the AddUpvote() method.
public void AddUpvote(Vote upvote, IEmailerService emailer)
{
Upvotes.Add(upvote);
if ( Upvotes.Count == 10 )
{
emailer.Send(Owner.EmailAddr);
}
}
Detect this state within AddUpvote() and have AddUpvote() resolve an IEmailService from an IoC container (instead of passing the IEmailerService as a parameter).
Detect this state in the external service object that invokes question.AddUpvote().
public void UpvoteClickHandler(Question question)
{
question.AddUpvote(new Upvote());
if ( question.Upvotes.Count == 10 )
{
_emailer.Send(question.Owner.EmailAddr);
}
}
Your better solution here!
You really don't want to mix these two together since they have separate concerns. Let the Question class care about questions and the message service care about what to do when the voting hits 10, or 20, or 100 or...
The following example is meant for demonstration purposes only, but you will get the point. There is a clear separation of concerns, so the Question class doesn't have to change if the requirements for sending messages changes. Remember according to the SOLID principles, a class should only have one reason to change.
public class Question
{
public string Description { get; set; }
public Int32 Votes { get; set; }
public User Owner { get; set; }
public event EventHandler<QuestionEventArgs> OnUpvote;
private void RaiseUpvoteEvent(QuestionEventArgs e)
{
var handler = OnUpvote;
if (handler != null) handler(this, e);
}
public void Upvote()
{
Votes += 1;
RaiseUpvoteEvent(new QuestionEventArgs(this));
}
}
public class MessageService
{
private Question _question;
public MessageService(Question q)
{
_question = q;
q.OnUpvote += (OnUpvote);
}
private void OnUpvote(object sender, QuestionEventArgs e)
{
if(e.Question.Votes > 10)
SendMessage(e.Question.Owner);
}
}
public class QuestionEventArgs: EventArgs
{
public Question Question { get; set; }
public QuestionEventArgs(Question q)
{
Question = q;
}
}
So there you have it. There are a lot of other ways to accomplish this, but the event model is a great way to go, and it accomplishes the separation of concerns you want in your implementation in order to make maintenance earlier.
Both options 1) and 2) jump out as being the wrong place to send out an email. A Question instance shouldn't know these two things:
It shouldn't know about the policy, ie when to send out an email.
It shouldn't know about the mechanics of notification for a policy, ie the email service.
I know that this is a matter of taste, but you're tying in the Question closely with both a policy as well as the mechanism to send out an email. It would be really hard to move this Question class to another project (like ServerFault, which is StackOverflow's sister site for instance)
I'm interested in this question, because I'm creating a notification system for a Help Desk that I am building. This is what I did in my system:
Create a NotificationManager (Basically, completely move the concern of notifications to a separate class).
public Class NotificationManager
{
public void NotificationManager(NotificationPolicy policy, IEmailService emailer)
{
}
}
I then did something along the lines of this (The UpvoteClickHandler has a dependency to a NotificationManager instance):
public void UpvoteClickHandler(Question question)
{
question.AddUpvote(new Upvote());
_notificationManager.Notify(Trigger.UpvoteAdded, question);
}
All the UpvoteClickHandler does is tell NotificationManager that an upvote was added to question and let NotificationManager determine whether and how it should send out an email.
The answer depends on your fundamental approach to application and object design. And (edit here) what you view as your most important trait of the system. Looks like you have data, questions, and business rules, up votes. Not question objects at all. So you should treat your data as data and allow data tools to work on them, by not mixing behavior into them. Traditional object design would have all the behaviors and data in the object, so sending eMail would belong in the object. (option 1 and 2) I guess this is the black box, or self contained object approach. Modern practices, as I've come to learn, has objects as simple data holders. Which are meant to be moved around, persisted, transformed and have stuff done to them. Perhaps living as little more than structs in C. The behavior comes from the services and transformations that are applied to the simple objects.
HI all,
In my opinion "sends an email notification to a question's owner whenever his/her question receives 10 upvotes" is domain logic and therfore it should be into domain object, in order to avoid an anemic domain.
It's the action of sending the email (i.e. communicate with smtp server) that MUST go into the infrastructure layer.
So i think that option 1 is not totally wrong. Keep in mind that you can always test your object by passing a mock implementation of the IEmailerService.
Best Regards,
Stefano