Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java - where should I put my domain object logic?

I am developing a java-spring project and I have a packagegr.serafeim.domain which contains all my domain classes (for instance, Student, School etc - they are concrete classes). All these have relations between them through JPA annotations. Until now everything was fine, but now I need to implement methods to these classes that would need to query the database to get their results.

How should I implement these methods ? My first choice would be to put it inside the domain classes, however in order to do that I'd need to include references to the data repositories in all my domain classes. I don't like this very much -- is this a good design choice ? Should I implement interfaces that my domain classes would implement ? Can you propose a better solution -- what is the common practice in such cases ?

TIA

like image 337
Serafeim Avatar asked Nov 25 '13 06:11

Serafeim


People also ask

Should domain objects have logic?

The theory states that the domain objects should encapsulate their behaviour and business logic. A model which contains only data and has its logic somewhere outside is called an "anemic domain model", which is a bad thing. Also, the domain should not perform data access.

What is Java domain object?

A domain class represents a table column and it allows you to handle the column value as a Java object. In the Doma framework, a domain means all the values which a data type may contain. In short, a domain class is a user defined class that can be map to a column. The use of the domain classes is optional.

What is domain folder in Java?

Usually a domain folder contains POJOs (Plain Old Java Objects). This folder basically stores classes that might or might not be entities, but follow a common structure: fields. construstors. getters and setters.

What is domain object?

The domain object model is based on the Decision Optimization Center Application Data Model. It provides a simple way to map tables to Java classes, columns to attributes, and foreign keys to bidirectional references.


2 Answers

My answher: no, don't place references to repositories into you domain models. Place them into business services instead. And don't manage any security into domain at all. Security is refered to use cases, not domain logic, so security is placed over domain.

And I disagree with Sandhu. I'd use the following architecture:

  1. Model classes. They don't get getters/setters for everything. It depends on model logic. Otherwise you get model where you can easily break consistency. Or where are many unobvious things. Suppose you have User.registrationDate field. When you construct a new User object, you should not forget to field registrationDate field by hands. So, just place registrationDate initialization in your constructor and remove setter!
  2. Repository interface just inside your model. Suppose you have some business logic that depends on existing stored objects. You can't expliciltly refer from your domain logic into infrastructure dependencies like JPA, Hibernate, JDBC, etc. So you query this stored objects from interfaces.
  3. Business services (optionally). They implement some complex logic, involving many different entities, not including security and transaction management. Your question is about it. Yes, if you need query for entities in your domain logic, place query into repository and call it from your business service.
  4. Repository implementation inside infrastructure package. Implements repository interfaces using JPA or mockito or whatever else. They also don't include neither security nor transactions.
  5. Application services (optionally). If there is some complex interaction with infrastructure or security checking.
  6. Remote facade interface. Client and server communicate only through remote facade interface.
  7. Remote facade implementation (controller). Transforms thick entity objects into thin DTOs (data transfer objects). All transaction demarcation and security is here (often using annotations).

This approach conforms DDD style, described by Martin Fowler. I think that JPA is musused in most of modern projects. It is used not as persistence provider, but as active record. Active record is not domain model implementation pattern, but database abstraction pattern. So if you really want transaction script approach, use some active record library or something like MyBatis instead of heavyweight JPA provider.

Also I don't understand the need of DAO. JPA providers do data abstraction themselves, don't they? Also data abstraction is not about model, but about infrastructure. So why is DAO placed over model? If you do really need DAO, you should place it under model (into repository implementation, I suppose).

Example of right usage:

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/

like image 98
Alexey Andreev Avatar answered Oct 05 '22 04:10

Alexey Andreev


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

  1. Model Classes (@Entity)- Exactly your domain classes
  2. Dao Interfaces
  3. Dao Implemetations (@Repository)
  4. Service Interfaces
  5. Service Implementations (@Service)
  6. Controller classes (@Controller)

2 & 3 forms the Persistence Layer and 4 & 5 forms 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);
}

Dao 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);
    }
}
like image 42
Sandhu Santhakumar Avatar answered Oct 05 '22 03:10

Sandhu Santhakumar