Disclaimer: I'm completely new to Event Sourcing, Axon Framework and DDD, so there is quite big possibility I'm doing something wrong.
I'm doing an application to manage events - conferences, workshops etc.
I have one aggregate root, which is EventProposal. There should be a possibility to assign a TodoList to given EventProposal. TodoList is another aggregate root, consisting of TodoItems - entities. TodoItem can be edited, mark as done etc.
There is a requirement of assigning TodoItem to TodoList, which I've implemented this way:
public class TodoList extends AbstractAnnotatedAggregateRoot {
#AggregateIdentifier
private TodoListId id;
#EventSourcedMember
private List<TodoItem> todoItems = Lists.newArrayList();
...
public void assignTodoItem(TodoItemId todoItemId, String content, LocalDate creationDate) {
if (alreadyHasTodoItemWith(todoItemId)) {
apply(new TodoItemNotAssignedToTodoList(
id, todoItemId)
);
return;
}
apply(new TodoItemAssignedToTodoListEvent(
id, todoItemId, content, creationDate)
);
}
#EventSourcingHandler
public void on(TodoItemAssignedToTodoListEvent event) {
final TodoItem item = TodoItemFactory.create(
event.todoItemId(),
event.description(),
event.createdAt()
);
todoItems.add(item);
}
Corresponding success-path command and event:
#Value
#Accessors(fluent = true)
public class AssignTodoItemToTodoListCommand {
#TargetAggregateIdentifier
private final TodoListId todoListId;
private final TodoItemId todoItemId;
private final String description;
private final LocalDate createdAt;
}
#Value
#Accessors(fluent = true)
public class TodoItemAssignedToTodoListEvent {
private final TodoListId todoListId;
private final TodoItemId todoItemId;
private final String description;
private final LocalDate createdAt;
}
This could be easly tested with Axon's BDD-way. (GivenThenFixture)
But now - there is another requirement: one should be able to create TodoList upon existing TodoListTemplate. Template is just an aggregate that wrapps a collection of TodoItemTemplates.
And there comes my implementation problem. I tried something like (in TodoList class):
public void fulfillWith(TodoListTemplate todoListTemplate, LocalDate creationDate) {
if (alreadyHasAnyTodoItem()) {
apply(new TodoListNotFulfilledWithTemplateEvent(
id,
todoListTemplate.id()
)
);
return;
}
apply(new TodoListFulfilledWithTemplateEvent(
id,
todoListTemplate.id(),
todoListTemplate.todoItemDescriptions(),
creationDate
)
);
}
#EventSourcingHandler
public void on(TodoListFulfilledWithTemplateEvent event) {
todoItems.addAll(
fromDescriptions(event.todoItemDescriptions(), event.fulfilledAt())
);
}
private Collection<TodoItem> fromDescriptions(Collection<String> descriptions, LocalDate creationDate) {
return descriptions.stream()
.map(description -> TodoItemFactory.create(description, creationDate))
.collect(Collectors.toList());
}
Again, commands and events:
#Value
#Accessors(fluent = true)
public class FulfillTodoListWithTemplateCommand {
private final TodoListId todoListId;
private final TodoListTemplateId todoListTemplateId;
private final LocalDate creationDate;
}
#Value
#Accessors(fluent = true)
public class TodoListFulfilledWithTemplateEvent {
private final TodoListId todoListId;
private final TodoListTemplateId todoListTemplateId;
private final List<String> todoItemDescriptions;
private final LocalDate fulfilledAt;
}
Problem:
As you can see - there is TodoItemFactory class involved, generating an unique ID:
public static TodoItem create(String content, LocalDate createdAt) {
return TodoItemFactory.create(nextId(), content, createdAt);
}
This way it can't be tested with axon - it gives me error org.axonframework.test.AxonAssertionError: Illegal state change detected!
This is quite obvious - working aggregate's ID is different than the one's constructed after aplying events.
Finally, my question arrives:
How should I solve this?
Generate those IDs somewhere and include them in outcoming TodoListFulfilledWithTemplateEvent? This leads to having two collections in an event - one for IDs, one for item contents/descriptions.
Assume that I got those IDs generated before and include them not only in outcoming Event, but also in incoming Command. This leads to same ugliness as before, but twice.
Do it in the way that I invoke 'assingTodoItemToTodoList' method multiple times, which will generate a lot of events and could produce reordering as events are async.
Sorry for verboseness, I tried to be as specific as possible.
Related
I'm new in DDD so I'm doing some practice to undertand a little bit more. I have Course BC with the follow rules:
Course has to be created first and then they can create the modules of one course
Every module will be finished by the user when he upload the homework
The course will be finished by the user when he finished all the modules
Definition:
A course covers a particular topic and it is comprised of module. For instance, sap course has 10 modules such as: module 1: what is it?, module 2: how to use it?…
After this, I realize that Course is the aggregate root of module, because the modules are finished I have to close the status of user with the course.
the model would be:
public class Course : AggregateRoot
{
private string title;
private List<Module> modules;
}
but also module is an aggregate root of homework because when the user upload his homework the module has to be closed. This make me think that this approach is wrong because is not possible in DDD have nested aggregate root. Someone knows what is it wrong?
[UPDATED]
Ok, now I understand how is work and why you split it in 2 BC. However I did some changes and some questions come to my mind.
-I've created enroll method as static and I put the constructor as private.
-Course have to be an array because one student can have more than one.
-I've put more parameters related with the course and also the teacher. Is the teacher and entity of course, right?
-I created status of course to update it when the module is finished this way I don't have to read all the modules to know it. is ok?
-How can I pass more information for every module like title and description? and is the course entity how create all the modules, right?
public class StudentEnrolment: AggregateRoot
{
private StudentId studentId;
private Course courses;
private constructor(
StudentId studentId,
Course course,
){
this.studentId= studentId;
this.courses[] = course;
}
public statuc function enroll(
StudentId studentId,
CourseId courseId,
string courseTitle,
string courseLink,
string teacherId,
string teacherName,
List<Tuple<ModuleId, string>> modules) {
teacher = new Teacher(...);
courseStatus = new courseStatus();
new course(courseTitle, courseLink, courseStatus, teacher);
return new self(studentId, course);
}
public function void uploadModuleHomework(ModuleId moduleId, Homework homework){
/* forward to course.uploadModuleHomework */
}
public boolean isCourseFinished(){
/* forward to course.isFinished */
}
public List<Tuple<ModuleId, string>> getModules(){
/* forward to course.getModules */
}
}
There are two different sub-domains (so we have two bounded contexts):
1.Courses and modules administration where the teachers can administer those; Here Course and Module can be Aggregate roots and a course could hold references to the Modules IDs (not to instances!).
public class Course: AggregateRoot
{
private string title;
private List<ModuleId> modules;
}
2.Student participations to the courses. Here there is a StudentEnrolment Aggregate root that contains references to the Course and Module from the other BC but as Value objects; it models the student participation to a single course; in this bounded context there is a new Entity, Homework, that track the student homework-upload and course participation status.
public class StudentEnrolment: AggregateRoot
{
private StudentId studentId;
private Course course;
private List<Homework> homeworks;
// initialize a student enrolment as public constructor or make constructor private and use a static method
// here is important to notice that only this AR creates its entities, it does not receive them as parameter
public constructor(
StudentId studentId,
Course course,
List<Module> modules){
this.studentId = studentId;
this.course = course;
//build the the homeworks entity list based on the modules parameter
//for each module create a Homework entity, that initially is not uploaded, like:
this.homeworks = modules.map(module => new Homework(module))
}
public function void uploadFileForHomework(ModuleId moduleId, string file){
/* find the homework by module Id and upload file*/
}
public boolean isCourseFinished(){
/*returns true if all homeworks are uploaded*/
/*optimization: you could have a status that is updated when a homework's file is uploaded*/
}
public List<Tuple<ModuleId, string, boolean>> getHomeworks(){
/* returns a list of readonly Homeworks, i.e. Tuple<ModuleId, string /*module title*/, boolean /*is uploaded*/> */
}
}
public class Homework: Entity
{
private Module module;
private string file;
public constructor(Module module){
this.module = module;
}
public void upload(string file){ this.file = file;}
public boolean isUploaded(){return (boolean)this.file;}
public string getUploadedFile(){return this.file;}
public ModuleId getModuleId(){return this.module.getId();}
}
public class Course: ValueObject
{
private string title;
private CourseId id;
public constructor(id, title){...}
public string getTitle(){return this.title;}
public string getId(){return this.title;}
}
public class Module: ValueObject
{
private string title;
private string description;
private ModuleId id;
public constructor(id, title, description){...}
public string getTitle(){return this.title;}
public string getDescription(){return this.description;}
public string getId(){return this.title;}
}
If you need to query the Enrolment to get the homeworks you should not return a list of Homeworks because the client code would think that it can call Homework.upload(file) directly, which is not permitted (only the Aggregate root can modify its internal entities). Instead, you could return a Tuple or better, you can create an immutable version of the Homework class.
Please find the below code for the DAO & Entity Object and Accessor
#Table(name = "Employee")
public class Employee {
#PartitionKey
#Column(name = "empname")
private String empname;
#ClusteringColumn(0)
#Column(name = "country")
private String country;
#Column(name = "status")
private String status;
}
Accessor:
#Accessor
public interface EmployeeAccessor {
#Query(value = "SELECT DISTINCT empname FROM EMPLOYEE ")
ResultSet getAllEmployeeName();
}
}
DAO getAllEmployeeNames returns a List which are employee names
and it will be sorted in ascending order.
DAO
public class EmployeeDAOImpl implements EmployeeDAO {
private EmployeeAccessor employeeAccessor;
#PostConstruct
public void init() {
employeeAccessor = datastaxCassandraTemplate.getAccessor(EmployeeAccessor.class);
}
#Override
public List<String> getAllEmployeeNames() {
List<Row> names = employeeAccessor.getAllEmployeeName().all();
List<String> empnames = names.stream()
.map(name -> name.getString("empname")).collect(Collectors.toList());
empnames.sort(naturalOrder()); //sorted
return empnames;
}
}
JUnit Test(mockito):
I am not able to mock the List[datastax row]. How to mock and returns a list of rows with values "foo" and "bar".Please help me in unit test this.
#Category(UnitTest.class)
#RunWith(MockitoJUnitRunner.class)
public class EmployeeDAOImplUnitTest {
#Mock
private ResultSet resultSet;
#Mock
private EmployeeAccessor empAccessor;
//here is the problem....how to mock the List<Row> Object --> com.datastax.driver.core.Row (interface)
//this code will result in compilation error as we are mapping a List<Row> to the ArrayList<String>
//how to mock the List<Row> with a list of String row object
private List<Row> unSortedTemplateNames = new ArrayList() {
{
add("foo");
add("bar");
}
};
//this is a test case to check if the results are sorted or not
//mock the accessor and send rows as "foo" & "bar"
//after calling the dao , the first element must be "bar" and not "foo"
#Test
public void shouldReturnSorted_getAllTemplateNames() {
when(empAccessor.getAllEmployeeName()).thenReturn(resultSet);
when(resultSet.all()).thenReturn(unSortedTemplateNames); //how to mock the List<Row> object ???
//i am testing if the results are sorted, first element should not be foo
assertThat(countryTemplates.get(0), is("bar"));
}
}
Wow! This is overly complex, hard to follow, and not an ideal way to write unit tests.
Using PowerMock(ito) along with "static" references in your own code is not recommended and is a sure sign of a code smells.
First, I am not sure why you decided to use a static reference (e.g. EmployeeAccessor.getAllEmployeeName().all(); inside the EmployeeDAOImpl class, getAllEmployeeNames() method) instead of using the instance variable (i.e. empAccessor), which is more conducive to actual "unit testing"?
The EmployeeAccessor, getAllEmployeeName() "interface" method is not static (clearly). However, seemingly, whatever this (datastaxCassandraTemplate.getAccessor(EmployeeAccessor.class);) generates makes it so (really?), which then requires the use of PowerMock(ito), o.O
Frameworks like PowerMock, and extensions of (i.e. "PowerMockito"), were meant to test and mock code used by your application (unfortunately, but necessarily so) where this "other" code makes use of statics, Singletons, private methods and so on. This anti-pattern really ought not be followed in your own application design.
Second, it is not really apparent what the "Subject Under Test" (SUT) is in your test case. You implemented a test class (i.e. EmployeeDAOImplTest) for, supposedly, your EmployeeDAOImpl class (the actual "SUT"), but inside your test case (i.e. shouldReturnSorted_getAllTemplateNames()), you are calling... countryLocalizationDAOImpl.getAllTemplateNames(); thus testing the CountryLocalizationDAOImpl class (??), which is not the "SUT" of the EmployeeDAOImplTest class.
Additionally, it is not apparent that the EmployeeDAOImpl even uses a CountryLocalizationDAO instance (assuming an interface here as well), and if it does, then it is certainly something that should be "mocked" when the EmployeeDAOImpl "interacts" with instances of CountryLocalizationDAO, particularly in the context of a unit test. The only correlation between the EmployeeDAO and CountryLocalizationDAO is that the Employee has a country field.
There are a few other problems with your design/setup as well, but anyway.
Here are a few suggestions...
First, let's test what your EmployeeDAOImplTest is meant to test... EmployeeDAO.getAllEmployeeNames() in a sorted fashion. This in turn may give you ideas of how to test your "CountryLocalizationDAO, getAllTemplateNames() method perhaps (if it even makes sense, i.e. getAllTemplateNames() is in fact dependent on an Employee's country, when Employees are ordered by name (i.e. "empname" and accessed via EmployeeAccessor).
public class EmployeeDAOImpl implements EmployeeDAO {
private final EmployeeAccessor employeeAccessor;
// where does the DataStaxCassandraTemplate reference come from?!
private DataStaxCassadraTemplate datastaxCassandraTemplate = ...;
public EmployeeDAOImpl() {
this(datastaxCassandraTemplate.getAccessor(EmployeeAccessor.class));
}
public EmployeeDAOImpl(EmployeeAccessor employeeAccessor) {
this.employeeAccessor = employeeAccessor;
}
protected EmployeeAccessor getEmployeeAccessor() {
return this.empAccessor;
}
public List<String> getAllEployeeNames() {
List<Row> nameRows = getEmployeeAccessor().getAllEmployeeName().all();
...
}
}
Then in your test class...
public class EmployeeDAOImplUnitTest {
#Mock
private EmployeeAccessor mockEmployeeAccessor;
// SUT
private EmployeeDAO employeeDao;
#Before
public void setup() {
employeeDao = new EmployeeDAOImpl(mockEmployeeAccessor);
}
protected ResultSet mockResultSet(Row... rows) {
ResultSet mockResultSet = mock(ResultSet.class);
when(mockResultSet.all()).thenReturn(Arrays.asList(rows));
return mockResultSet;
}
protected Row mockRow(String employeeName) {
Row mockRow = mock(Row.class, employeeName);
when(mockRow.getString(eq("empname")).thenReturn(employeeName);
return mockRow;
}
#Test
public void getAllEmployeeNamesReturnsSortListOfNames() {
when(mockEmployeeAccessor.getAllEmployeeName())
.thenReturn(mockResultSet(mockRow("jonDoe"), mockRow("janeDoe")));
assertThat(employeeDao.getAllEmployeeNames())
.contains("janeDoe", "jonDoe");
verify(mockEmployeeAccessor, times(1)).getAllEmployeeName();
}
}
Now, you can apply similar techniques if in fact there is an actual correlation between Employees and CountryLocalizationDAO via the EmployeeAccessor.
Hope this helps get you on a better track!
-j
i have code like below
ScheExeService.ScheduleId scheduleID = ScheExeService.getOneTimeScheduleId(nl.getCompany(), workItem.getId());
ScheExeService.Schedule schedule = ScheExeService.loadSchedule(nl, scheduleID.getId());
Calendar cal = Calendar.getInstance(java.util.TimeZone.getTimeZone(nl.getTimeZone()));
cal.setTime(schedule.attributes.startDate.toDate());
AND i am trying to mock "schedule.attributes.startDate.toDate()"
and ScheExeService look like below
public class ScheExeService implements blah
{
public final static class Schedule {
public final ScheduleId id;
public final ScheduleAttributes attributes;
public final String callableClassName;
public final long itemId;
public Schedule(ScheduleId id, ScheduleAttributes attributes, String callableClassName, long itemId)
{
this.id = id;
this.attributes = attributes;
this.callableClassName = callableClassName;
this.itemId = itemId;
}
}
public final static class ScheduleAttributes {
public final #Nullable LocalDate startDate;
public final #Nullable LocalTime startTime;
public final #Nullable LocalDate endDate;
public final #Nullable LocalTime endTime;
Method code
}
}
and i tried
doNothing().doThrow(new RuntimeException()).when(mockedCalenderObject).setTime(null);
but i am keep getting NPE.
how i can avoid schedule.attributes.startDate.todate() call..?
It's a good idea not to mock data/value objects like List and String, including Joda objects like LocalDate and LocalTime and your data objects like Schedule and ScheduleAttributes. This is mostly because, unless the instances are hard to create, you can just create instances in your test and have the getters and setters behave as expected.
See also: "Test smell: Everything is mocked"
Based on your code, I'd very much suggest mocking loadSchedule (which may require refactoring ScheExeService away from static methods, refactoring the second half of your system under test to accept an arbitrary Schedule, or using PowerMock) and creating your test Schedule using real calls.
public void yourMethod(NL nl, WorkItem workItem) {
ScheExeService.ScheduleId scheduleID = ScheExeService.getOneTimeScheduleId(nl.getCompany(), workItem.getId());
ScheExeService.Schedule schedule = ScheExeService.loadSchedule(nl, scheduleID.getId());
processSchedule(schedule);
}
/**
* Processes schedule for yourMethod. Exposed for testing: Create as many
* arbitrary Schedules as you want and pass them in to test this thoroughly.
*/
void processSchedule(Schedule schedule) {
Calendar cal = Calendar.getInstance(java.util.TimeZone.getTimeZone(nl.getTimeZone()));
cal.setTime(schedule.attributes.startDate.toDate());
// ...
}
This has baffled me for a while now and I cannot seem to get the grasp of it. I'm using Cell Value Factory to populate a simple one column table and it does not populate in the table.
It does and I click the rows that are populated but I do not see any values in them- in this case String values. [I just edited this to make it clearer]
I have a different project under which it works under the same kind of data model. What am I doing wrong?
Here's the code. The commented code at the end seems to work though. I've checked to see if the usual mistakes- creating a new column instance or a new tableview instance, are there. Nothing. Please help!
//Simple Data Model
Stock.java
public class Stock {
private SimpleStringProperty stockTicker;
public Stock(String stockTicker) {
this.stockTicker = new SimpleStringProperty(stockTicker);
}
public String getstockTicker() {
return stockTicker.get();
}
public void setstockTicker(String stockticker) {
stockTicker.set(stockticker);
}
}
//Controller class
MainGuiController.java
private ObservableList<Stock> data;
#FXML
private TableView<Stock> stockTableView;// = new TableView<>(data);
#FXML
private TableColumn<Stock, String> tickerCol;
private void setTickersToCol() {
try {
Statement stmt = conn.createStatement();//conn is defined and works
ResultSet rsltset = stmt.executeQuery("SELECT ticker FROM tickerlist order by ticker");
data = FXCollections.observableArrayList();
Stock stockInstance;
while (rsltset.next()) {
stockInstance = new Stock(rsltset.getString(1).toUpperCase());
data.add(stockInstance);
}
} catch (SQLException ex) {
Logger.getLogger(WriteToFile.class.getName()).log(Level.SEVERE, null, ex);
System.out.println("Connection Failed! Check output console");
}
tickerCol.setCellValueFactory(new PropertyValueFactory<Stock,String>("stockTicker"));
stockTableView.setItems(data);
}
/*THIS, ON THE OTHER HAND, WORKS*/
/*Callback<CellDataFeatures<Stock, String>, ObservableValue<String>> cellDataFeat =
new Callback<CellDataFeatures<Stock, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(CellDataFeatures<Stock, String> p) {
return new SimpleStringProperty(p.getValue().getstockTicker());
}
};*/
Suggested solution (use a Lambda, not a PropertyValueFactory)
Instead of:
aColumn.setCellValueFactory(new PropertyValueFactory<Appointment,LocalDate>("date"));
Write:
aColumn.setCellValueFactory(cellData -> cellData.getValue().dateProperty());
For more information, see this answer:
Java: setCellValuefactory; Lambda vs. PropertyValueFactory; advantages/disadvantages
Solution using PropertyValueFactory
The lambda solution outlined above is preferred, but if you wish to use PropertyValueFactory, this alternate solution provides information on that.
How to Fix It
The case of your getter and setter methods are wrong.
getstockTicker should be getStockTicker
setstockTicker should be setStockTicker
Some Background Information
Your PropertyValueFactory remains the same with:
new PropertyValueFactory<Stock,String>("stockTicker")
The naming convention will seem more obvious when you also add a property accessor to your Stock class:
public class Stock {
private SimpleStringProperty stockTicker;
public Stock(String stockTicker) {
this.stockTicker = new SimpleStringProperty(stockTicker);
}
public String getStockTicker() {
return stockTicker.get();
}
public void setStockTicker(String stockticker) {
stockTicker.set(stockticker);
}
public StringProperty stockTickerProperty() {
return stockTicker;
}
}
The PropertyValueFactory uses reflection to find the relevant accessors (these should be public). First, it will try to use the stockTickerProperty accessor and, if that is not present fall back to getters and setters. Providing a property accessor is recommended as then you will automatically enable your table to observe the property in the underlying model, dynamically updating its data as the underlying model changes.
put the Getter and Setter method in you data class for all the elements.
If i have designed an AR like the below, how do you think i should go about say updating a property in one of the order line objects ?
For Example how can i change the title for one of my order lines (example question)
This is the Order Aggregate Root
public class Order
{
private readonly int id;
private readonly Customer customer; // Customer is another Aggregate
private readonly IList<OrderLine> orderLines;
private readonly IOrderLineFactory orderLineFactory;
public Order(int id, Customer customer, IOrderLineFactory orderLineFactory)
{
this.id = id;
this.customer = customer;
this.orderLines = new List<OrderLine>();
this.orderLineFactory = orderLineFactory;
}
public void AddOrderLine(Item item, int quantity)
{
OrderLine orderLine = orderLineFactory.Create(this, item, quantity);
orderLines.Add(orderLine);
}
}
Order order = orderRepository.find(orderId);
order.changeTitle(orderLineId, "New title");
Where 'orderLineId' can be a line number or an index or something else as long as it is aggregate-root-specific (not a global id). Please see this answer to a similar question.