mercredi 1 juillet 2020

How can I apply the principles of "Functional core, imperative shell"?

I have stumbled upon Gary Bernhardt's "Functional core, imperative shell" pattern and I find it and correlates with many other architectures such as the Hexagonal Architecture.

I have been trying to apply these principles but with no "success".

For instance, say I have a service AddUser. AddUser is the imperative shell. It needs to do 3 things:

  1. Check if a user with same username/email is already taken in the database
  2. Validate the user
  3. Insert the user in the database

Here it is:

class AddUser {
  execute (username, email, password) {
    if (this.userRepository.findUserByUsername(username)) { // tries to find an user with same username
      throw new Error('Oops, username is already taken!')
    } else if (this.userRepository.findUserByEmail(email)) { // tries to find an user with same email
      throw new Error('Oops, email is already taken!')
    }
    
    if (!this.userValidator.validateUsername(username)) { // calls the UserValidator to validate username
      throw new Error('Oops, username is invalid!')
    } else if (!this.userValidator.validateEmail(email)) { // calls UserValidator to validate email
      throw new Error('Oops, email is invalid!')   
    }

    const hashedPassword = this.hasher.hash(password)

    return await this.userRepository.insert({ username, email, hashedPassword })
  }

The issue, however, is that I think this is not a true "imperative" shell as described by Gary and I would have to resort to mocking and stubbing to see if it behaves properly.

How would I go about making this more imperative?

Should I create a method validate() in UserValidator so it handles all the conditions?

Should the UserValidator throw the error instead of the AddUser service?

What about checking if user is already existent in the database?

Aucun commentaire:

Enregistrer un commentaire