Introduction
At this moment in time, I'm in the process of creating some fluent interface, as a result, I've created a DSL style solution for authentication & authorisation.
As a result, the consuming code-base may look something along the lines of this:
public class Demo {
public String login() throws IllegalStateException {
JWTState jwt = beginAuth()
.authentication()
.username("JoeBloggs")
.password("Secure_Pa$$w0rD!-#0962")
.authenticate()
.whenAuthenticationFails(e -> log.error("Authentication failure", e))
.then()
.authorisation()
.adGroup("Basic")
.authorise()
.whenAuthorisationFails(e -> log.error("Authorisation failure", e))
.then()
.jwt()
.generateJwt()
.whenJwtFails(e -> log.error("Unable to generate JWT", e))
.getJwt();
// Safety net, rather than return null or an empty string, throw this exception
return Optional.of(jwt.isValid())
.filter(Boolean::valueOf)
.map(x -> jwt.token())
.orElseThrow(() -> new IllegalStateException("Unable to generate JWT"));
}
}
Defensive Example
Since I've gone with a defensive approach, the consuming code-base does not need to worry about exceptions that are thrown, since they're handled internally. Although, these exceptions can be visible to the consuming code upon request, as you can see with the 'whenXFails'
methods. Internally, there's a lot of logic going on, an example being one of the guard clauses, an example being this snippet from the password guard clause:
// Start of class....
@Override
public void validate(String password) throws IllegalArgumentException {
argument(password).
regex(VALID_PASSWORD_REGEX).
message("Supplied password is null").whenNull().
message("Supplied password is empty").whenEmpty().
message("Supplied password is too short").whenLessThan(MINIMUM_PASSWORD_LENGTH).
message("Supplied password is too long").whenGreaterThan(MAXIMUM_PASSWORD_LENGTH).
message("Supplied password does not match the valid password regex").whenNoRegexMatch();
}
// End of class....
To ensure that there's a good ability to log errors & debug this code-base, I've tried to handle nearly every scenario I can think of, expected & unexpected. In which case, you can see above that there are many specific cases where the password validation will fail, but from the consumer's perspective this isn't necessarily visible.
In the event that the consuming code-base wishes to proceed through this unit of work, it will merely alter the current state. Referring back to the Demo
class, in the event of some failure, the getJwt()
method will return some JWTState
such as InvalidJWTState
, this is to ensure that something is always returned to the consumer, meaning that the consumer need not check for null values. I've also taken the time to apply the null object design pattern to improve the defensiveness of this code.
Conclusion
Before I ramble on & on, I'd just like to cut to the chase, first off, in this instance, am I applying good practices? If not can anyone make any recommendations on how I could improve the overall quality of this code-base? Secondly, are there better methods that I'm merely not aware of?
As a result, the consuming code-base may look something along the lines of this:
indicates that the code is not concrete from a working project. That makes this question off-topic for code review. \$\endgroup\$