2

I'm developing an app with a user management system. There is a database table named user with the following columns:

| Column Name     | Column Type |
|-----------------|-------------|
| userId          | BIGINT      |
| email           | TEXT        |
| firstName       | TEXT        |
| lastName        | TEXT        |
| passwordDigest  | TEXT        |
| birthday        | DATE        |
| address         | TEXT        |

The most straightforward design is to create a Hibernate entity called User:

@Entity
@Table(name = "user")
public class User {
    @Id
    private long userId;
    private String email;
    private String firstName;
    private String lastName;
    private String passwordDigest;
    private LocalDate birthday;
    private String address;
}

However, the general user info (i.e. email, first name, last name, birthday, address) and password are getting updated in separate pages in my app. Specifically, there is a "Edit Your General Info" page for users to update their general info, and there is a separate "Update Your Password" page for users to update their passwords. Just like lots of other apps.

Therefore, if I'm using the User entity above, the code for each pages will be:

void updateUserGeneralInfo (User newUser) {
    User oldUser = userDao.getExistingUser(newUser.getUserId());
    newUser.setPasswordDigest(oldUser.getPasswordDigest());
    userDao.updateUser(newUser);
}

void updateUserPassword (long userId, String newPassword) {
    User oldUser = userDao.getExistingUser(userId);
    oldUser.setPasswordDigest(calculateDigest(newPassword)); // auto save to DB by Hibernate
}

The problem lies in updateUserGeneralInfo(). Since newUser is passed from GUI, it doesn't contain a passwordDigest. Therefore, directly calling updateUser(newUser) would wipe out the user's passwordDigest in the DB. To avoid that, it's necessary to retrieve the user's existing entity just to fill in the password digest, so that updateUser(newUser) won't affect the user's passwordDigest. This is kind of clunky and hard to maintain.

To solve the problem, I'm thinking about creating 2 Hibernate entities that maps to the same database table user:

@Entity
@Table(name = "user")
public class UserGeneralInfo {
    @Id
    private long userId;
    private String email;
    private String firstName;
    private String lastName;
    private LocalDate birthday;
    private String address;
}

@Entity
@Table(name = "user")
public class UserCredential {
    @Id
    private long userId;
    private String passwordDigest;
}

The code for the above-mentioned pages will then be:

void updateUserGeneralInfo (UserGeneralInfo newUserGeneralInfo) {
    userDao.updateUserGeneralInfo(newUserGeneralInfo);
}

void updateUserPassword (long userId, String newPassword) {
    UserCredential newUserCredential = new UserCredential(userId, calculateDigest(newPassword));
    userDao.updateUserCredential(newUserCredential);
}

With this new design, the code is much clearer because the responsibility for each method is isolated. updateUserGeneralInfo() won't worry about wiping out user's passwordDigest anymore, and updateUserPassword() won't touch any part of the user table other than userId and passwordDigest.

My question is: is the new design (i.e. separating UserGeneralInfo and UserCredential rather than a single User) a really good design? Is there any disadvantage that I'm not aware of? Furthermore, is it a common design pattern for generic user management systems? Thanks!

1
  • Separating users management and security is rather normal. Both are the same thing (a user) seen from different angles, however, it doesn't mean they have to be persisted in a single table. I find your solution to be weird. It's half a solution because you are just making up the code to provide 2 methods with a sort of sense. Not a good reason to make things more complicated. Why don't you just make 2 different tables?
    – Laiv
    Commented Jan 18, 2021 at 9:10

1 Answer 1

1

To avoid that, it's necessary to retrieve the user's existing entity just to fill in the password digest, so that updateUser(newUser) won't affect the user's passwordDigest

There is an alternative that maybe you want to consider. An existing entity can be updated in a more controlled way:

void updateUserGeneralInfo (User newUser) {
    User oldUser = userDao.getExistingUser(newUser.getUserId());
    oldUser.setEmail(newUser.getEmail());
    // ...
    userDao.updateUser(oldUser);
}

Updating the old user, keeps the old passwordDigest and allows you to have more control over what is updated. When the number of fields to update is relatively small this is an approach to consider.

When the user will have many fields you can consider using a DTO and a mapper.

is the new design (i.e. separating UserGeneralInfo and UserCredential rather than a single User) a really good design?

Separating the user object into two more specific entities is ok, but in general, you want to create two tables as @Laiv points out. The design you suggest is unusual and could have the following issues:

  • Operations that need email and password (e.g. login, admin management) now require two queries, done manually.
  • Coupling:

there is a "Edit Your General Info" page ..., and there is a separate "Update Your Password" page ... Therefore, ... , the code for each pages will be:

It is better to avoid direct coupling between the GUI and how the data is persisted. If the GUI changes (and it will) everything you are doing now will probably need to change.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.