I'm building a library application. Let's assume that we have a requirement to let registered people in the library to borrow a book for some default period of time (4 weeks).
I started to model my domain with an AggregateRoot called Loan with code below:
public class Loan : AggregateRoot<long>
{
public static int DefaultLoanPeriodInDays = 30;
private readonly long _bookId;
private readonly long _userId;
private readonly DateTime _endDate;
private bool _active;
private Book _book;
private RegisteredLibraryUser _user;
public Book Book => _book;
public RegisteredLibraryUser User => _user;
public DateTime EndDate => _endDate;
public bool Active => _active;
private Loan(long bookId, long userId, DateTime endDate)
{
_bookId = bookId;
_userId = userId;
_endDate = endDate;
_active = true;
}
public static Loan Create(long bookId, long userId)
{
var endDate = DateTime.UtcNow.AddDays(DefaultLoanPeriodInDays);
var loan = new Loan(bookId, userId, endDate);
loan.Book.Borrow();
loan.AddDomainEvent(new LoanCreatedEvent(bookId, userId, endDate));
return loan;
}
public void EndLoan()
{
if (!Active)
throw new LoanNotActiveException(Id);
_active = false;
_book.Return();
AddDomainEvent(new LoanFinishedEvent(Id));
}
}
And my Book entity looks like this:
public class Book : Entity<long>
{
private BookInformation _bookInformation;
private bool _inStock;
public BookInformation BookInformation => _bookInformation;
public bool InStock => _inStock;
private Book(BookInformation bookInformation)
{
_bookInformation = bookInformation;
_inStock = true;
}
public static Book Create(string title, string author, string subject, string isbn)
{
var bookInformation = new BookInformation(title, author, subject, isbn);
var book = new Book(bookInformation);
book.AddDomainEvent(new BookCreatedEvent(bookInformation));
return book;
}
public void Borrow()
{
if (!InStock)
throw new BookAlreadyBorrowedException();
_inStock = false;
AddDomainEvent(new BookBorrowedEvent(Id));
}
public void Return()
{
if (InStock)
throw new BookNotBorrowedException(Id);
_inStock = true;
AddDomainEvent(new BookReturnedBackEvent(Id, DateTime.UtcNow));
}
}
As you can see I'm using a static factory method for creating my Loan aggregate root where I'm passing an identity of the borrowing book and the user identity who is going to borrow it. Should I pass here the references to these objects (book and user) instead of ids? Which approach is better? As you can see my Book entity has also a property which indicates the availability of a book (InStock property). Should I update this property in the next use-case, for example in the handler of LoadCreatedEvent? Or should it be updated here within my AggregateRoot? If it should be updated here inside my aggregate I should pass the entire book reference instead of just an ID to be able to call it's method _book.Borrow().
I'm stuck at this point because I would like to do it pretty correct with the DDD approach. Or am I starting to do it from the wrong side and I'm missing something or thinking in a wrong way of it?
DomainEvents are in-memory events that are handled within the same domain.
You commit or rollback the entire "Transaction" together. Consider Domain Event as a DTO, which needs to hold all the information related to what just happened in the domain. So, as long as you have that information I do not think it matters if you pass Id, or the entire object.
I would go for passing the id in the domain event though as that information is sufficient to pass on the information to the DomainEventHandler.
Also, refer to this example of a similar scenario in Microsoft Docs, where they only pass UserId and CardTypeId along with all the other relevant information in the Domain event.
public class OrderStartedDomainEvent : INotification {
public string UserId { get; }
public int CardTypeId { get; }
public string CardNumber { get; }
public string CardSecurityNumber { get; }
public string CardHolderName { get; }
public DateTime CardExpiration { get; }
public Order Order { get; }
public OrderStartedDomainEvent(Order order,
int cardTypeId, string cardNumber,
string cardSecurityNumber, string cardHolderName,
DateTime cardExpiration)
{
Order = order;
CardTypeId = cardTypeId;
CardNumber = cardNumber;
CardSecurityNumber = cardSecurityNumber;
CardHolderName = cardHolderName;
CardExpiration = cardExpiration;
} }
There are a couple of things that look suspicious in your sample code:
The first one, Loan does the following:
loan.Book.Borrow();
but it doesn't have a reference to Book and, at first sight, it doesn't seem it should either.
The second one, your Book entity seems to have many responsibilities: hold book information like Author, title, subject, hold stock information, and manage the Borrowing state. This is far too many responsibilities for an aggregate, let alone for an entity within an aggregate. Which also begs the question, does a Book really belong to a Loan? it seems strange.
I would recommend, rethinking your aggregates and try to give them a single purpose. What follows is purely as an example on the type of thinking that you could do, not a proposed design:
First, it makes sense to have a Book somewhere, which holds the book information. You can manage book information and Author information completely independent from the rest of the system. In fact, this part would look pretty much the same for a book store, a library, a publishing company, and an e-commerce site.
As you are modeling a Library, it probably makes sense to have something like LibraryItem (domain experts will know the right word for this). This item might have a type (book, DVD, magazine, etc) and the id of the actual item, but it doesn't care about the Title, Description, etc. with the Id is enough. Potentially, it also stores the location/sorting of the item with the library. Also, this Item might have some sort of Status, let's say Active/Retired. If it's Active, the item exists in the Library. If it's Retired, it doesn't exist anymore. If you have multiple items of the same book, you'll simply create more Items with the same BookId and if it's possible to identify the concrete physical book, with a bar code, for example, each Item will have that unique code, so you can find it by scanning the bar code.
Now a Loan, to me, it's basically an ItemId plus a CustomerId (not sure if they are called customers in this domain). Every time a Customer wants to borrow an Item, the user will find the Item (maybe scanning the bar code), and find the Customer. At this point you have to create the Loan with the CustomerId, ItemId, date and not a lot more I would say. This could be an aggregate on itself or simply be managed by the Item. Depending on what you chose the implementation will vary obviously. Note that you don't reuse Loans. You'll have somewhere a list of Loans that you can query and this list won't be inside the Item aggregate. This aggregate only needs to make sure that 2 loans of the same item are not allowed at the same time.
Well, that was a rather long preliminary explanation to answer your question: you need to manage the InStock in the same aggregate that allows you to Borrow a book. It has to be transactional, because you want to ensure that a book is not borrowed multiple times at once. But instead of passing one aggregate to the other, design your aggregates so that they have the right data and responsibilities together.
This is my Entity:
#Data
#Entity
#IdClass(EtlJobExecutionTriggersId.class)
#Table(name = "ETL_JOB_EXEC_TRIGGERS")
public class EtlJobExecutionTriggers {
#Id private Long jobExecIdUs;
#Id private Long jobExecIdDs;
private LocalDate cobDate;
}
And here is the Composite Primary Key Class:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Embeddable
#EqualsAndHashCode
public class EtlJobExecutionTriggersId implements Serializable {
private Long jobExecIdUs;
private Long jobExecIdDs;
}
And here is my Spring Repo:
public interface EtlJobExecTriggersRepo extends JpaRepository<EtlJobExecutionTriggers, EtlJobExecutionTriggersId> {
String SQL_ = "select o from EtlJobExecutionTriggers o where o.jobExecIdDs in (:ids) or o.jobExecIdUs in (:ids) order by o.jobExecIdUs, o.jobExecIdDs";
#Query(EtlJobExecTriggersRepo.SQL_)
List<EtlJobExecutionTriggers> findAllByJobExecIdDsInAndJobExecIdUsInSQL(#Param("ids") List<Long> jobExecIdList);
}
The #Query works as expected, but I would like not to write any SQL and instead express the same Query using only Spring Data repository query.
I have tried the following (and other variants)
List<EtlJobExecutionTriggers> findAllByJobExecIdDsInAndJobExecIdUsInOrderByJobExecIdUsJobExecIdDs(List<Long> jobExecIdDsList)
But i keep getting errors when Booting. The above interface method yields the following exception for the OrderBy part:
org.springframework.data.mapping.PropertyReferenceException: No property jobExecIdDs found for type Long! Traversed path: EtlJobExecutionTriggers.jobExecIdUs.
So what am I doing wrong here? or is it not possible to express this particular query via Spring Data Repo query?
As I have written in my comment I fixed the Order by issue, but I am still unable to make it work with only one method parameter (List jobExecIdList)
When I make it with two (List jobExecIdDsList, List jobExecIdUsList)
Like this:
List<EtlJobExecutionTriggers> findAllByJobExecIdDsInAndJobExecIdUsInOrderByJobExecIdUsAscJobExecIdDsAsc(List<Long> jobExecIdDsList, List<Long> jobExecIdUsList);
it actually works but I can't get to work with only one list, as in the #Query("....") method
I think using your own custom id generator conflicts with Spring Data Repository query.
// You shoud have two parameters in your method as below.
List findAllByJobExecIdDsInAndJobExecIdUsInOrderByJobExecIdUsJobExecIdDs(List jobExecIdDsList,List jobExecIdUsList);
Using Value Objects can have a lot of advantages, especially when it comes to the type strictness of it. Using a PersonKey to use a Person (where the PersonKey really is a wrapped Long) is a lot safer than just using a Long or String as-is. I was wondering what the recommended strategy to deal with this in Spring Data is, however. Setting up the Repository is of course a matter of for example using
public interface PersonRepository CrudRepository<Person, PersonKey> {
}
but I was wondering what the best way to make the PersonKey class would be, having it map easily. Is there a better option than using an EmbeddedKey?
There is two annotations to do it : IdClass or EmbeddedId. I would recommend to use EmbeddedId because you don't have to repeat all of your attributes of your id class into your entity class.
Let's say you use EmbeddedId. It would looks like this :
#Embeddable
public class PersonKey {
private Long id;
}
#Entity
public class Person {
#EmbeddedId
private PersonKey personKey;
}
And you will access to your id like this :
select p.personKey.id from Person p
But with IdClass, your Person class would look like this :
#Entity
#IdClass(Person.key)
public class Person {
#Id
private Long id;
}
And you will access like this :
select p.id from Person p
In our project (springMVC) Rest API project I wish to only use ONE model for both request and response (to avoid having to add tons of code to copy field from object to object)
I'd like to use Swagger to handle all the doc, but I'm running into a little problem. For example let say I have a model User
public class User {
private Long id;
private String username;
private String password;
}
And a simple controller
public void createUser(#RequestBody User user)...
public User getUser(Long id) ..
Now I would like swagger to hide the property password on deserialization but not serialization (so having it display for the Input but the output)
and the opposite for the Id field.
I have tried using #JsonIgnore coupled with #JsonProperty but on the swagager-ui it either displays everything or hides everything. I cannot manage to it work.
Could someone indicate me what is the best way of archiving my goal ? Is it possible to use a single model for request and response while using swagger? In case it is not possible to use #JsonIgnore, is there a way to archive this differently ?
Swagger doesn't want you to have different input/output models with the same name. You should simply create an interface and attach that to the input, and for the output extend that interface or add an implementation with the additional field. For example, please see here for modeling tips:
https://swaggerhub.com/api/swagger-tutorials/modeling-samples/1.0.0
Your exact use case is one of them. The solution posted in the above link is here:
definitions:
User:
description: this is a user that would be passed into the system
properties:
username:
type: string
UserResponse:
allOf:
- $ref: '#/definitions/User'
- type: object
required:
- id
properties:
id:
type: string
format: uuid
readOnly: true
where User is the input object, and UserResponse is the output object, with the additional id field.
Add #JsonIgnore with getter of the field and #JsonProperty with the setter or with the field . As Due to use of immutable code or final fields sometime setter doesn't work.
example :
public class Student {
private Float name;
private String rollnum;
private String section;
#JsonProperty
private Boolean passOrFailed;
#JsonIgnore
public Boolean getpassOrFailed {
return active;
}
}
Remember to use both else else it will lead to removing element in deserialization
How to fetch data from two tables based upon User id ?
*****************Role Class***********************
#Entity
#Table(name = "IUC_CON_USER_ROLE_MAP")
public class Role {
#Id
#Column(name="F_ROLE_MAP_ID")
int rolemap;
#Column(name="F_ROLE_ID")
int roleid;
#OneToMany(mappedBy="role",fetch=FetchType.EAGER)
Set<User> F_USER_ID;
*********************User class*******************
#Entity
#Table(name = "IUC_CON_USER")
public class User implements Serializable {
#Id
#Column(name = "F_USER_ID")
private int id;
#Column(name = "F_USER_NAME")
private String name;
#Column(name = "F_USER_PWD")
private String pwd;
#ManyToOne
#JoinColumn(name="F_USER_ID",insertable=false,updatable=false)
private Role role;
----------setter and getter for properties
}
External edit:
Criteria creation code:
DetachedCriteria uCrit = DetachedCriteria.forClass(User.class, "user");
uCrit.add(Restrictions.eq("user.id", 5));
uCrit.setProjection(Projections.property("user.id"));
DetachedCriteria criteria = DetachedCriteria.forClass(Role.class, "role");
criteria.add(Property.forName("role.F_USER_ID").in(uCrit));
List lt1 = criteria.getExecutableCriteria(getSession()).list();
use createAlias
criteria.createAlias("propertiy_of_main_entity", "aliastName");
For me this fetches entities asociated with root entity via some entity property name;
in your case something like
createAlias("F_USER_ID", "roleUsers");
should do.
Anyway why ins't your field name following commong naming doctrine for Java? WHY_IS_IT_UPPERCASED_WITH_DASHES_LIKE_CONSTANTS_?
All in all everything is explained in Hibernate documentation. If alias won't work, than fetch associations like in examples from Hibernate documentation under link I provided.
You can use below series of statements to fetch User along with the Role:
int userId = 1;
Criteria criteria = session.createCriteria(User.class);
criteria.setFetchMode("role", FetchMode.JOIN);
criteria.add(Restrictions.eq("id", userId));
User user = (User) criteria.uniqueResult();
The default fetching strategy of Hibernate is to fetch the associations lazily, which you need to override at runtime in code, if you want the associations (here the Role) to be fetched along with User. The third statement is doing this overriding by seting the fetch mode to JOIN. This statement FetchMode.JOIN overrides the default behavior, so that Role will be fetched along with the User.