Child pages
  • example UserService
Skip to end of metadata
Go to start of metadata

A UserService. Anatomized.

So why a UserService? Well, we thought a while about making an example with a TomatoService, but in the end we assumed that most of our readers will have users in their platforms and the need to manage them, but only few will actually manage tomatoes. Or potatoes.

What is the UserService all about? We understand user as a system entity needed to operate with the system. For this matter the UserService contains everything needed by the system to perform basic, unspecific operations with the User, like login. Therefore a UserService is very important in most portals and is getting a huge load of traffic.

So, what are the attributes of a user we need to maintain:

  • we need a user name as login credential.
  • we need some kind of userid to refer to a user internally.
  • a registration date is of interest.
  • user should have a status (member, paying member, blocked - we just save an int and let other components interpret it, for the simplicity of the example)
  • finally we need to have a password for login and the last login date.

This leads us to the following User object:


public class User {
	
	private UserId userId;
	private String userName;
	private String email;
	private int status;
	private long lastLoginTimestamp;
	private long registrationTimestamp;
}

You may have noticed that the password field is missing. This is for a reason. For security reasons we will not let the application to read user's passwords, but let the service maintain it. This will allow the service to store only hashed versions of the passwords.

Our User object needs a constructor:

	public User(UserId aUserId){
		if (aUserId==null)
			throw new IllegalArgumentException("Null userId is not allowed");
		this.userId = aUserId;
	}

and some standard methods:

	@Override public boolean equals(Object o){
		return o instanceof User ? 
				((User)o).userId.equals(userId) : false;
	}
	
	@Override public int hashCode(){
		return getUserId().hashCode();
	}

We will also offer getter methods for all fields and setter methods for all fields except userId.
Since we don't allow construction of User objects without a UserId, we can safely assume that we always have a userId.
You may also have noticed that we don't rely on equals/hashCode methods generated by the ide. That's because they are bloated and useless. Our business rules say, that we can not have two distinct user objects with same userId. Therefor the userId object is completely sufficient for equality and hashing.

  • and now for the service interface -

interface

public interface UserService {

The CRUD part

first we add some crud methods:

	/**
	 * Creates a new user. Contains all absolutely required parameters.
	 * @param userName name of the user.
	 * @param password password of the user.
	 * @param email email of the user.
	 * @return
	 * @throws UserServiceException
	 */
	User createUser(String userName, String password, String email) throws UserServiceException;
	
	/**
	 * Returns the corresponding user object if present.
	 * @param id of the user. 
	 * @return
	 * @throws UserServiceException
	 * @throws NoSuchUserException if no user with corresponding id is found.
	 */
	User getUser(UserId id) throws UserServiceException, NoSuchUserException;
	/**
	 * Updates an existing user.
	 * @param user
	 * @return
	 * @throws UserServiceException
	 * @throws NoSuchUserException
	 */
	User updateUser(User user) throws UserServiceException, NoSuchUserException;
	
	/**
	 * Returns a list of requested users. 
	 * @param userIds
	 * @return
	 * @throws UserServiceException
	 */
	List<User> getUsers(List<UserId> userIds) throws UserServiceException;
	
	/**
	 * Deletes the user if present in the system. Otherwise the request does nothing.
	 * @param user
	 * @throws UserServiceException
	 */
	void deleteUser(User user) throws UserServiceException;
	
	/**
	 * Deletes the user if present in the system. Otherwise the request does nothing.
	 * @param userId
	 * @throws UserServiceException
	 */
	void deleteUser(UserId userId) throws UserServiceException;

What do those methods do?

User createUser(String userName, String password, String email) throws UserServiceException;

Creates a new user object. Actually this method is the only way to create a new user in the system. It becomes all needed information from the client, adds some additional information by itself (like registration timestamp), checks for constraints and returns a fresh shiny User object.

User getUser(UserId id) throws UserServiceException, NoSuchUserException;
User updateUser(User user) throws UserServiceException, NoSuchUserException;
List<User> getUsers(List<UserId> userIds) throws UserServiceException;
void deleteUser(User user) throws UserServiceException;
void deleteUser(UserId userId) throws UserServiceException;

Authentification methods

	/**
	 * Sets the password of the user to the submitted value. The application has to pre-validate the password.
	 */
	void setPassword(UserId userId, String password) throws UserServiceException;
	/**
	 * Returns true if the submitted password matches the stored password.
	 * @param userId
	 * @param submittedPassword
	 * @return
	 * @throws UserServiceException
	 */
	boolean doesPasswordMatch(UserId userId, String submittedPassword) throws UserServiceException;

	/**
	 * Executes the login operation. Note, the caller has to obtain a userId previously by calling getUserIdByCredential. The reason is that 
	 * the login operation manipulates data and there is no chance for a caching proxy to update the stored user data in a writethrough operation 
	 * if the call doesn't contain a userid.
	 * @param UserId
	 * @param password
	 * @return
	 * @throws UserServiceException
	 */
	User loginUser(UserId credentialName, String password) throws UserServiceException;
void setPassword(UserId userId, String password) throws UserServiceException;
boolean doesPasswordMatch(UserId userId, String password) throws UserServiceException;

Returns true if the submitted password matches the stored password. In fact only a hashed copy of a password is saved, therefore
the service is responsible for password checks.

User loginUser(UserId credentialName, String password) throws UserServiceException;

Helper methods

	/**
	 * Returns the user id associated with the given email.
	 * @param email
	 * @return
	 * @throws UserServiceException
	 * @throws NoSuchUserException
	 */
	UserId getUserIdByEmail(String email) throws UserServiceException, NoSuchUserException;
	
	/**
	 * Returns the userId associated with the given user name.
	 * @param userName
	 * @return
	 * @throws UserServiceException
	 * @throws NoSuchUserException
	 */
	UserId getUserIdByUserName(String userName) throws UserServiceException, NoSuchUserException;
	
	/**
	 * Returns the user by a login credential, which, in our case, is either a username or an email. 
	 * This method is needed to allow the client to call login with userId parameter, which is needed for proper routing of the login method.
	 * Client calls the login method in two parts:
	 * first call UserId targetUserId = userService.getUserIdByCredential(credential); <- routed to master or caching service.
	 * second call User targetUser = userService.loginUser(targetUserId, password); <- routed via mod-proxy
	 * @param credential
	 * @return
	 * @throws UserServiceException
	 * @throws NoSuchUserException
	 */
	UserId getUserIdByCredential(String credential) throws UserServiceException, NoSuchUserException;
UserId getUserIdByEmail(String email) throws UserServiceException, NoSuchUserException;
UserId getUserIdByUserName(String userName) throws UserServiceException, NoSuchUserException;
UserId getUserIdByCredential(String credential) throws UserServiceException, NoSuchUserException;
  • No labels