article banner

Exercise: UserService

The project will emulate a real-life task, typical to backend development, but similar to what is done on the domain layer in Android. We will create a service that will allow us to manage users. It will be a simple service, but it will be a good example of how to use Kotlin to create a domain layer.

All the files for solving this exercise can be found in the MarcinMoskala/kotlin-exercises project on GitHub in the functional/project package. You should clone this project and solve this exercise locally.

Your task is to implement methods from classes UserService, UserDtoFactory, and RealUserKeyGenerator.

UserService is a service that will allow us to manage users. It should have the following properties and methods:

  • userByIdCache and userByKeyCache - properties that define this class caching mechanism. They should use cache DSL builder to configure a cache with 1 minute of expiration time after both read and write, and it should configure how data should be loaded into the cache. The userByIdCache should load data using the userRepository.getUser method, and the userByKeyCache should load data using the userRepository.getUserByKey method. In both cases, when data is loaded, it should also be stored in the other cache.
  • getUser(id: String): User? - returns user with the given id or null if there is no such user.
  • getUserByKey(key: String): User? - returns user with the given key or null if there is no such user.
  • getToken(email: String, passwordHash: String): String - returns a token for the user with the given email if the password hash is correct. If there is no such user or the password hash is incorrect, it throws an exception with the message "Wrong email or password".
  • updateUser(token: String, userPatch: UserPatch): User - updates the user with the given token using the given patch. If there is no such user, it throws an exception with message "User not found".
  • addUser(token: String, addUser: AddUser): User - adds a new user using the given data. Only admin can add users, so if the user with the given token is not an admin, it throws an exception.
  • userStatistics(token: String): UserStatistics - returns statistics about users. If the user with the given token is not an admin, it throws an exception.

UserDtoFactory is a factory that creates UserDto objects. It should have the method produceUserDto(addUser: AddUser): UserDto that creates a UserDto object using the given AddUser object. It should use the timeProvider to get the current time, the uuidGenerator to generate an id and a key, and the userKeyGenerator to generate a key.

RealUserKeyGenerator is a generator that creates keys for users. It should have the method findPublicKey(name: String, surname: String): String? to return a key for the given name and surname. If there is no such key, it shoudld return null. It should use the userRepository to check if the key is available. This function should try different combinations of name and surname in a form transformed to a correct key value. Key should be lowercase and only include characters or digits. It should not include any special characters. It should also not be shorter than 4 characters. If the key is not available, it should return null. It should try to generate a key in the following order, but if none of those keys is available, it should generate a random key using the uuidGenerator:

  • "$name$surname"
  • "$surname$name"
  • "$name${surname.first()}"
  • "${name.first()}$surname"
  • "$surname${name.first()}"
  • "${surname.first()}$name"

More detailed explanation for each of those functions can be found on their comments. You can also find unit tests for those functions in their files. Remember to run them to check if your solution is correct.

Once you are done with the exercise, you can check your solution here.