Where should I put my domain object logic?

I am developing a java-spring project, and I have a package gr.serafeim.domain that contains all my domain classes (for example, Student, School, etc. - these are specific classes). All of them have relationships between them through JPA annotations. So far, everything was fine, but now I need to implement methods for these classes that would have to query the database to get their results.

How do I implement these methods? My first choice is to place it inside the domain classes, however, for this I will need to include links to data repositories in all classes of my domain. I don’t really like it - is this a good design choice? Should I implement interfaces that will implement domain classes? Can you suggest a better solution - what is the common practice in such cases?

TIA

+7
java spring architecture jpa
source share
2 answers

My answer is no, do not put links to repositories in your domain models. Instead, put them in business services. And do not manage any security in the domain at all. Security refers to use cases, not to domain logic, so security is placed on top of the domain.

And I do not agree with Sandhu. I would use the following architecture:

  • Model classes. They do not get getters / setters for everything. It depends on the model logic. Otherwise, you get a model where you can easily break consistency. Or where there are many unobvious things. Suppose you have a User.registrationDate field. When you create a new User object, you must not forget the field registrationDate field manually. So just put registrationDate in the constructor and delete setter!
  • The repository interface is only inside your model. Suppose you have a business logic that depends on existing stored objects. You cannot explicitly refer your domain logic to infrastructure dependencies such as JPA, Hibernate, JDBC, etc. Therefore, you are requesting these stored objects from interfaces.
  • Business services (optional). They implement some complex logic that includes many different objects, not including security and transaction management. Your question is about this. Yes, if you need a query for objects in your domain logic, put the query in the repository and call it from your business service.
  • Implementing storage within an infrastructure package. Implements repository interfaces using JPA or mockito or something else. They also do not include either security or transactions.
  • Application Services (optional). If there is a complex interaction with the infrastructure or a security check.
  • Remote facade interface. The client and server exchange data only through the remote facade interface.
  • Remote facade implementation (controller). Converts object entity objects to thin DTOs (data transfer objects). All demarcation and transaction security are here (annotations are often used).

This approach is consistent with the DDD style described by Martin Fowler. I think JPA is liked in most modern projects. It is not used as a persistence provider, but as an active record. An active record is not a domain model implementation model, but a database abstraction template. Therefore, if you really want to use a transaction script, use some kind of active record library or something like MyBatis instead of a reliable JPA provider.

And I do not understand the need for DAO. JPA providers do data abstraction themselves, right? Also, data abstraction is not about the model, but about the infrastructure. So why is the DAO placed above the model? If you really need a DAO, you should put it under the model (in the case of repository implementation, I suppose).

An example of proper use:

 package my.example.model; @Entity public class User { @Id @GeneratedValue private Integer id; private String login; private String password; @Temporal(TemporalType.TIMESTAMP) private Date registrationDate; User() { // for persistence provider only } public User(String login, String password) { this.login = login; this.password = hashPassword(password); this.registrationDate = new Date(); } public String getLogin() { return login; } public String setPassword(String password) { this.password = hashPassword(password); } public boolean matchPassword(String password) { return this.password.equals(hashPassword(password)); } public Date getRegistrationDate() { return registrationDate; } private static String hashPassword(String password) { try { MessageDigest digest = MessageDigest.getInstance("sha-1"); StringBuilder sb = new StringBuilder(); byte[] bytes = digest.digest(password.getBytes(charset)); for (byte b : bytes) { sb.append(Character.forDigit((b >>> 4) & 0xF, 16)).append(Character.forDigit(b & 0xF, 16)); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new AssertionError(e); } } } package my.example.model; public interface UserRepository { User findByLogin(String login); User findBySurrogateId(int id); Integer getSurrogateId(User user); boolean contains(User user); void add(User user); void delete(User user); } package my.example.infrastructure; @Component public class PersistentUserRepository implements UserRepository { @PersistenceContext private EntityManager em; public void setEntityManager(EntityManager em) { this.em = em; } @Override public User findByLogin(String login) { // I'd use QueryDSL here QUser qusr = new QUser("usr"); return new JPAQuery(em) .from(qusr) .where(qusr.login.eq(login)) .singleResult(qusr); } @Override public User findBySurrogateId(int id) { return em.find(User.class, id); } @Override public Integer getSurrogateId(User user) { return (Integer)em.getEntityManagerFactory().getPersistenceUnitUtil().getIdentity(user); } @Override public boolean contains(User user) { return em.contains(user); } @Override public void add(User user) { em.persist(user); } @Override public void delete(User user) { em.remove(user); } } package my.example.facade; public interface UserRemoteFacade { UserDTO getUser(String login); UserDTO getUser(int id); void changePassword(int userId, String newPassword); void registerUser(String login, String password) throws LoginOccupiedException; boolean authenticate(String login, String password); } package my.example.facade; public class UserDTO implements Serializable { private int id; private String login; private Date registrationDate; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getLogin() { return login; } public void setLogin(String login) { this.login = login; } public Date getRegistrationDate() { return registrationDate; } public void setRegistrationDate(Date registrationDate) { this.registrationDate = registrationDate; } } package my.example.server; @Transactional @Component public class UserRemoteFacadeImpl imlements UserRemoteFacade { private UserRepository repository; private Security security; @Autowired public UserRemoteFacadeImpl(UserRepository repository, Security security) { this.repository = repository; this.security = security; } @Override public UserDTO getUser(String login) { return mapUser(repository.findByLogin(login)); } @Override public UserDTO getUser(int id) { return mapUser(repository.findBySurrogateId(id)); } private UserDTO mapUser(User user) { if (user != security.getCurrentUser()) { security.checkPermission("viewUser"); } UserDTO dto = new UserDTO(); dto.setId(repository.getSurrogateId(user)); dto.setLogin(user.getLogin()); dto.setRegistrationDate(user.getRegistrationDate()); return dto; } @Override public void changePassword(int userId, String newPassword) { User user = repository.findByLogin(login); if (user != security.getCurrentUser()) { security.checkPermission("changePassword"); } user.setPassword(newPassword); } @Override public void registerUser(String login, String password) throws LoginOccupiedException { if (repository.findByLogin(login) != null) { throw new LoginOccupiedException(login); } User user = new User(login, password); repository.add(user); } @Override public boolean authenticate(String login, String password) throws LoginOccupiedException { User user = repository.findByLogin(login); return user != null && user.matchPassword(password); } } 

Also see this project: http://dddsample.sourceforge.net/

+8
source share

The best way to implement Spring is to have the following components in your project:

  • Model Classes ( @Entity ) - Exactly Your Domain Classes
  • Dao Interfaces
  • Tao Additions ( @Repository )
  • Service Interfaces
  • Service Implementations ( @Service )
  • Controller Classes ( @Controller )

2 and 3 form the Persistence Layer , and 4 and 5 form the Service Layer

Example:

Model class

 @Entity public class User implements Serializable { private static final long serialVersionUID = -8034624922386563274L; @Id @GeneratedValue @Column(name = "id") private int id; @Column(name = "name") private String name; public int getId() { return id; } public void setId(final int id) { this.id = id; } public String getName() { return name; } public void setName(final String name) { this.name = name; } } 

Dao Interface

 public interface UserDao { public User getUser(String username); } 

Tao implementation

 @Repository public class UserDaoImpl implements UserDao { @Autowired private SessionFactory sessionFactory; private Session openSession() { return sessionFactory.getCurrentSession(); } @Override public User getUser(String username) { List<User> userList = new ArrayList<User>(); Query query = openSession().createQuery( "from User u where u.username = :username"); query.setParameter("username", username); userList = query.list(); if (userList.size() > 0) return userList.get(0); else return null; } } 

Service interface

 public interface UserService { public User getUser(String username); } 

Service implementation

 @Service @Transactional public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override public User getUser(final String username) { return userDao.getUser(username); } } 
+5
source share

All Articles