Last active
March 4, 2023 15:45
-
-
Save gicappa/575eb6813b5815b70b7682ded2cc6389 to your computer and use it in GitHub Desktop.
Hexagonal Architecture
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package application; | |
/* ... */ | |
// Application level is the holder of primary drivers | |
// This is actually the specific implementation of the application server | |
// (SparkJava in this case) | |
public class Emapp implements Runnable { | |
private final Mapper mapper; | |
private final UserService userService; | |
/* ... */ | |
// run() method is the entry point of the application | |
public void run() { | |
// Maps "/users" routes | |
// Responsiblity: handles the HTTP requests and creates the HTTP response | |
post("/users", (request, response) -> { | |
try { | |
response.status(201); | |
// Invoke the userService level still at application level | |
return userService.create(request.body()); | |
} catch (EmappException ee) { | |
response.status(400); | |
return mapper.fromError(new Error(ee.getErrorCode(), ee.getMessage())); | |
} | |
}); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package application; | |
/* ... */ | |
// UserService decouples the controller (HTTP) from the parsing and sanitizing | |
// of the user inputs. | |
// If we need to change the ApplicationServer (e.g. from SparkJava to Tomcat) | |
// we can port this class in the new application server without changing the | |
// logic inside of it. | |
public class UserService { | |
private final ManageUsersUseCase manageUsersUseCase; | |
private final Mapper mapper; | |
public String create(String candidateUserPayload) { | |
var candidateUser = mapper.toCandidateUser(candidateUserPayload); | |
// These guards could be done both in the application level and in the domain | |
// If the front end should not allow to have them valued they should stay here | |
// because it is an exceptional since they should be valued. | |
// | |
// If the front end allows them to be null the check (if needed) should be done | |
// in the domain and handled as a functional requirement. | |
checkIsPresent("email", candidateUser.email()); | |
checkIsEmailValid("email", candidateUser.email()); | |
// Invoking the manageUsersUseCase the first object in the domain. | |
var userResponse = manageUsersUseCase.createUser(candidateUser); | |
return mapper.fromUser(userResponse); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package domain; | |
// Domain class coordinating the use case using collaborators | |
// to achieve a business need. | |
// | |
public class ManageUsersUseCase { | |
// interfaces inside domain package that don't have the implementation | |
// in the domain and which implementation are in the infrastructure level | |
// The concrete implementation is injected in the constructor of this class | |
private final UsersRepo usersRepo; | |
private final EmailService emailService; | |
public ManageUsersUseCase(UsersRepo usersRepo, EmailService emailService) { | |
this.usersRepo = usersRepo; | |
this.emailService = emailService; | |
} | |
// Here is contained the behaviour of the domain | |
public User createUser(CandidateUser candidateUser) { | |
// This method is suppose to persist the user (the domain doesn't care where) | |
// The returned user contains also the ID generated by the persistence layer | |
var user = usersRepo.recordUser(candidateUser); | |
// This method is sending an email calling the interface and | |
// not caring about the implementation library | |
// | |
// The email is also sending the ID of the new user in the email | |
emailService.sendVerificationEmailTo(user); | |
return user; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package domain; | |
//Interface still present in the domain | |
public interface UsersRepo { | |
User recordUser(CandidateUser user); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package domain; | |
// Interface in the domain | |
public interface EmailService { | |
void sendVerificationEmailTo(User user); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package infrastructure; | |
// Implementation specific for oracle database | |
// This is an adapter and implements the UserRepo interface | |
public class OracleUserRepo implements UsersRepo { | |
private final Logger logger = LoggerFactory.getLogger("repo|users"); | |
@Override | |
public User recordUser(CandidateUser user) { | |
user.roles().forEach(this::checkRoleExists); | |
String uuid = insertUserInOracle(user); | |
return new User(uuid, user.email(), user.roles()); | |
} | |
private void checkRoleExists(Role role) { | |
if (!selectRoleIdInDb(role)) { | |
throw new RoleDoesNotExistsException(role); | |
} | |
} | |
private String insertUserInDb(CandidateUser user) { | |
logger.info("insert the user({}) in the Oracle database and return the user id", user.email()); | |
return "00000000-0000-0000-0000-000000000000"; | |
} | |
private boolean selectRoleIdInDb(Role role) { | |
logger.info("select the role with id({}) in the role lists and return true if it present", role.id()); | |
return true; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Per cui ho tre domande:
Come struttureresti il codice?
Tu dove metteresti la business logic che dice: