DDD modelisation issue (entity accessing repository) - domain-driven-design

I am designing the model of the following business needs :
The application must be able to register Users
The steps of the User registration are :
The user enters an email address and confirm
A verification code is sent to the provided email address.
The user must enter the correct verification code to continue
Repeat steps 1-3 for a phone number with verification code by SMS (optional)
The user then enters some personal information and confirm => the account is created
After registration, the user can update his email address or mobile phone number, but must go through the same verification process (code sent which must be entered to confirm the modification)
I ended up with the following model :
Verifiable (interface)
User (entity)
EmailAddress (value type, is a Verifiable)
MobilePhoneNumber (value type, is a Verifiable)
RandomCode (value type)
VerificationCode (entity containing a Verifiable, a RandomCode and a generationDateTime)
VerificationEmail (aggregate containing a VerificationCode, an EmailAddress and a Locale)
VerificationSms (aggregate containing a VerificationCode, a MobilePhoneNumber and a Locale)
Then here come the questions !!
Is it correct to have the Verifiable interface in order to have a VerificationCode instead of having EmailVerificationCode and SmsVerificationCode ? (Although it's not really a part of the ubiquitous language)
As I must persist somewhere the tuple emailAddress/mobilePhoneNumber + randomCode + generationDateTime to be able to retrieve it for verification, is it ok to have a specific entity for this ?
When the user wants to update his email address I was expecting to do something like :
// In the application service
User u = userRepository.findByUid(uid);
u.updateEmailAddress(newEmailAddress, enteredCode);
userRepository.save(u);
// In the User class
public void updateEmailAddress(EmailAddress newEmailAddress, String code) {
// Here comes the direct repository access
VerificationCode v = verificationCodeRepository.findByVerifiable(newEmailAddress);
if (v != null && v.hasNotExpired() && v.equalsToCode(code)) {
this.emailAddress = newEmailAddress;
verificationCodeRepository.delete(v);
}
else {
throw new IncorrectVerificationCodeException();
}
}
but to prevent my entity accessing a repository I ended up with the following code :
// In the application service
User u = userRepository.findByUid(uid);
VerificationCode v = verificationCodeRepository.findByVerifiable(newEmailAddress);
if (v != null && v.hasNotExpired() && v.equalsToCode(code)) {
verificationCodeRepository.delete(v);
u.updateEmailAddress(newEmailAddress);
userRepository.save(u);
}
else {
throw new IncorrectVerificationCodeException();
}
// In the User class
public void updateEmailAddress(EmailAddress newEmailAddress) {
this.emailAddress = newEmailAddress;
}
But it looks like an anemic model and the business logic is now in the application layer...
I am really struggling to correctly design the model as this is my first DDD project, any advice, modelisation suggestion is welcomed...

There is nothing wrong passing a repository as an argument in your updateEmailAddress() method.
But there is a better alternative, a domain service:
Your domain service depends on the repository and encapsulates the logic bound to your verification. You then pass this service to the user entity which is in charge of calling the correct method.
Here is how it could looks like:
class EmailVerificationService {
VerificationCodeRepository repository;
boolean isCodeVerified(EmailAddress emailAddress, String code) {
// do your things with the repository
// return true or false
}
}
Then in the user class:
class User {
// ...
public void updateEmailAddress(EmailVerificationService service, EmailAddress emailAddress, String code) {
if (service.isCodeVerified(emailAddress, code)) {
this.emailAddress = emailAddress;
} else {
// throw business Exception ?
}
}
}
In your application service, you inject the domain service and wire everything, catching the eventual exception and returning an error message to the user.

This is a suggestion of modeling, if you want to take it into account. Hope it could help you. I would model it this way:
User (aggregate root entity)
id
emailAddress (not null and unique)
mobilePhoneNumber (optional)
personalInfo
enabled (a user is created disabled when the registration process starts, and it is enabled when the process ends successfully)
VerificationCode (aggregate root entity) ===> it is associated to a user
id
randomCode
expirationDate
userId
smsOption (boolean) ===> if sms option is true, this verification code will be sent in a SMS to the user (otherwise it will be sent by email to the user)
Static Factory meethods:
forSendingByEmail ==> creates an instance with smsOption false
forSendingBySMS ===> creates and instance with smsOption true
Domain Service: sendVerificationCodeToUser ( verificationCodeId ) ===> checks smsOption to send either an SMS or an email (to the mobilePhoneNumber/emailAddress of the associated userId)
DomainEvent: VerificationCodeWasCreated ===> it has the id of the verification code that has been created
Raised by the VerificationCode constructor
The listener will call the domain service: sendVerificationCodeToUser(verificationCodeWasCreated.verificationCodeId())
THE REGISTRATION PROCESS (application service methods):
(1) The user enters an email address and confirm
public void registerUser ( String email ):
checks that doesn't exists any enabled user with the given email
if exist a disable user with the email, delete it
creates and persist a new disabled user with the email
creates and persist a new verification code associated to the created user for sending by email
(2) A verification code is sent to the provided email address ===> it is done by the domain event listener
(3) The user must enter the correct verification code to continue ===> the user who was sent the email in step (1) has to enter the email again, and the code he received)
public boolean isARandomCodeCorrectForUserEmail ( String randomCode, String email ) {
User user = userRepository.findByEmail(email);
if (user==null) {
return false;
}
VerificationCode vcode = verificationCodeRepository.findByRandomCodeAndUserId(randomCode,user.id());
if ( vcode==null) {
return false;
}
return vcode.hasNotExpired();
}
(4) Repeat steps 1-3 for a phone number with verification code by SMS (optional)
(4.1) The user of step (3) enters mobile phone number (we know the user id):
public void generateCodeForSendingBySmsToUser ( String mobilePhoneNumber, String userId ):
update user of userId with the given mobilePhoneNumber
creates and persist a new verification code associated to the user for sending by SMS
(4.2) The event listener sends the SMS
(4.3) The user who was sent the SMS in step (4.2) has to enter the email of step (1) again, and the code he received by SMS ===> isARandomCodeCorrectForUserEmail(randomCode,email)
(5) The user then enters some personal information and confirm ===> the account is created ===> what I do is enabling the user, since the user is already created, and we know the userId from step (3) or (4.3)
public void confirmRegistration ( PersonalInfo personalInfo, String userId ):
update user of userId with the given personalInfo
enables de the user
THE EMAIL/MOBILEPHONENUMBER MODIFICATION PROCESS:
It is similar to the registration, but the email/mobilePhoneNumber entered at the beginning must belongs to an existing enabled user, and at the end an update of the user is performed, instead of enabling.
ENABLED/DISABLED USERS:
Having enabled and disabled users, makes you taking it into account in authentication and authorization methods. If you don't want to or you're not allowed to have enabled/disabled users, you would have to model another aggregate that it would be UserCandidate or something like that, just with id, email and mobilePhoneNumber. And at the end of the process, create the real user with those values.

Related

nlapiSendEmail returns SSS_AUTHOR_MUST_BE_EMPLOYEE from correct employee id (on Sandbox)

in a Sandbox environment nlapiSendEmail (defined inside a suitelet) returns SSS_AUTHOR_MUST_BE_EMPLOYEE even when the sender id is correct
My distribution is Kilimanjaro, with SuiteScript 1.0. I have an administrator role, when calling nlapiSenEmail() directly from the backend model with my employee id, the email was sent to my employee profile, but not to the specified email, which is really a company distribution list. Even when I did not specify the logged customer email, a copy was sent to the logged customer email, a gmail account. The backend model operates only for the MyAccount application. It's worth noting that in this scenario nlapiSendEmail() return value was undefined. In my experience, Netsuite is really ambiguous in its behavior returning values or just functioning in an expected way, due to the "execution context". So, with the same data I put my call inside a suitelet, and now I am having the return SSS_AUTHOR_MUST_BE_EMPLOYEE.
function sendEmailWithAPI(request, response)
{
var senderId = request.getParameter('senderId');
var to = request.getParameter('emailTo');
var subject = request.getParameter('subject');
var body = request.getParameter('body');
var cc = request.getParameter('emailCC');
var result = {success:false, errorInfo:''};
try
{
var sendingResult = nlapiSendEmail(senderId, to, subject, body, cc);
result.success = true;
}
catch (errorOnMailSending)
{
result.returnValue = sendingResult;
result.errorInfo = errorOnMailSending.details;
}
response.write(JSON.stringify(result));
}
What is the record type of the senderId? NetSuite only accepts Employee records as sender of script generated emails. Also in Sandbox accounts, the emails are re-routed to the logged in user, specific list, or not at all. This is actually based on the Company preference in your Sandbox account. The reason for this is Sandbox is usually used for testing and you don't want to send test emails to actual customers.
In the end the "problem" was that I was just working in the Sandbox, as I prepared a snippet to test email sending in production everything went right. In the sandbox you can still send emails specifying a list at the subtab "Email options"
with the option "SEND EMAIL TO (SEPARATE ADDRESSES WITH COMMAS)"
this is located at Setup > Company > Email Preferences.

How to check if user with a specific ID exists?

I have to loop through all Rows in a table that contain a user field. I have to retrieve those users and do nasty stuff with them:
private void GetUsrInfo(FieldUserValue user, ClientContext clientContext) {
int id=user.LookupId;
Web web = clientContext.Web;
User spuser = web.GetUserById(id);
clientContext.Load(spuser);
clientContext.ExecuteQuery();
Mail = spuser.Email;
}
This works. However these are "old" entries and a lot of these persons do not even exist anymore. The user-field still contains the data of that now abandoned user, but when I try to retrieve the userdata by GetUserById() I retrieve the following exception:
Microsoft.SharePoint.Client.ServerException: User cannot be found.
at
Microsoft.SharePoint.Client.ClientRequest.ProcessResponseStream(Stream
responseStream) at
Microsoft.SharePoint.Client.ClientRequest.ProcessResponse()
Currently I just catch these Exceptions and proceed to the next user.
But this is bad and very slow.
Is there a more smart way? Anything like "if web.UserExists(id)..."?
EDIT
One possible way to check whether or not the user exists, without throwing an error or creating a new user (as result of the web.EnsureUser(#"domain\username") method) is to load the complete collection of users locally and use a LINQ statement to lookup the user by Id.
For example:
UserCollection collUser = ctx.Web.SiteUsers;
ctx.Load(collUser);
ctx.ExecuteQuery();
var user = collUser.Cast<User>().FirstOrDefault(u => u.Id == 1);
if (null != user)
{
Console.WriteLine("User: {0} Login name: {1} Email: {2}",
user.Title, user.LoginName, user.Email);
}
If there is a record where the ID == 1, it will be returned, if not the return value will be null.
Depending on the number of users in the site, this may have performance concerns, however, based on the number of exceptions you expect to generate checking the user ID, this solution may be feasible.
Reference: Csom or rest to verify user

Sitecore Droplink for User Roles

I'm building a custom workflow where all users that are members of a specific role will receive email notifications depending on certain state changes. I've begun fleshing out e-mail templates via Sitecore items with replaceable tokens, but I'm struggling to find a way to allow the setting of the recipient role in Sitecore. I'd like to avoid having users enter a string representation of the role, so a droplink would be ideal if there were a way to populate it with the various roles defined in sitecore. Bonus points if I can filter the roles that populate the droplink.
I'm aware that users/roles/domains aren't defined as items in the content tree, so how exactly does one go about configuring this droplink?
Sitecore 6.5.
I'm not sure if there is a module for this already made, but you can use this technique: http://newguid.net/sitecore/2013/coded-field-datasources-in-sitecore/
It explains how you can use a class as data source. So you could create a class that lists all user roles.
You might want to take a look at http://sitecorejunkie.com/2012/12/28/have-a-field-day-with-custom-sitecore-fields/ which presents a multilist to allow you to select a list of users.
Also take a look at the Workflow Escaltor Module form which you can borrow the AccountSelector control which allows you to select either individual person or roles.
This is the module I previously used to do this exact thing. The following code gets all the unique email addresses of users and only for those users that have read access to the item (it was a multisite implementation, the roles were restricted to each site but the workflow was shared).
protected override List<string> GetRecipientList(WorkflowPipelineArgs args, Item workflowItem)
{
Field recipientsField = workflowItem.Fields["To"];
Error.Assert((recipientsField != null || !string.IsNullOrEmpty(recipientsField.Value)), "The 'To' field is not specified in the mail action item: " + workflowItem.Paths.FullPath);
List<string> recepients = GetEmailsForUsersAndRoles(recipientsField, args);
if (recepients.Count == 0)
Log.Info("There are no users with valid email addresses to notify for item submission: " + workflowItem.Paths.FullPath);
return recepients;
}
//Returns unique email addresses of users that correspond to the selected list of users/roles
private List<string> GetEmailsForUsersAndRoles(Field field, WorkflowPipelineArgs args)
{
List<string> emails = new List<string>();
List<User> allUsers = new List<User>();
AccountSelectorField accountSelectorField = new AccountSelectorField(field);
List<Account> selectedRoles = accountSelectorField.GetSelectedAccountsByType(AccountType.Role);
List<Account> selectedUsers = accountSelectorField.GetSelectedAccountsByType(AccountType.User);
foreach (var role in selectedRoles)
{
var users = RolesInRolesManager.GetUsersInRole(Role.FromName(role.Name), true).ToList();
if (users.Any())
allUsers.AddRange(users);
}
selectedUsers.ForEach(i => allUsers.Add(Sitecore.Security.Accounts.User.FromName(i.Name, false)));
foreach (var user in allUsers)
{
if (user == null || !args.DataItem.Security.CanRead(user)) continue; //move on if user does not have access to item
if (!emails.Contains(user.Profile.Email.ToLower()))
{
if(user.Profile.Email != null && !string.IsNullOrEmpty(user.Profile.Email.Trim()))
emails.Add(user.Profile.Email.ToLower());
else
Log.Error("No email address setup for user: " + user.Name);
}
}
return emails;
}

Spring security integration with open id in grails

I am working on Integrating spring security with openId for my grails Application using springsecurity core and springsecurity openid plugins. I have integrated it, and it works well but I need to access the email for the logged in person. How can I get that, all that I am able to access is a token which is used for identifying the person.
Thanks to Ian Roberts.
He gives me this reply,Which exactly solves my problem.
His reply was:
As it happens I implemented exactly this in one of my applications
yesterday :-) Unfortunately it's not an open-source app so I can't just
point you at my code but I can explain what I did.
The spring-security-openid plugin supports the "attribute exchange"
mechanism of OpenID, although the support is not documented much (if at
all). How well it works depends on the provider at the far end but this
at least worked for me using Google and Yahoo.
In order to request the email address from the provider you need to add
the following to Config.groovy:
grails.plugins.springsecurity.openid.registration.requiredAttributes.email
= "http://axschema.org/contact/email"
Now to wire that into your user registration process you need an email
field in your S2 user domain class, and you need to edit the generated
OpenIdController.groovy in a few places.
add an email property to the OpenIdRegisterCommand
in the createAccount action there's a line
"if(!createNewAccount(...))" which passes the username, password and
openid as parameters. Change this along with the method definition to
pass the whole command object instead of just these two fields.
in createNewAccount pass the email value forward from the command
object to the User domain object constructor.
And finally add an input field for email to your
grails-app/views/openId/createAccount.gsp.
You can do the same with other attributes such as full name.
grails.plugins.springsecurity.openid.registration.requiredAttributes.fullname
= "http://axschema.org/namePerson"
The important thing to wire it together is that the thing after the last
dot following requiredAttributes (fullname in this example) must match
the name of the property on the OpenIdRegisterCommand.
Regards
Charu Jain
I've never used the springsecurity openid plugin, but when using springsecurity core you can expose additional information about the current user by implmenting a custom UserDetails. In my app, I added this implementation, so that I can show the name property of logged-in users. You'll need to change this slightly, so that the email address is exposed instead
/**
* Custom implementation of UserDetails that exposes the user's name
* http://grails-plugins.github.com/grails-spring-security-core/docs/manual/guide/11%20Custom%20UserDetailsService.html
*/
class CustomUserDetails extends GrailsUser {
// additional property
final String name
CustomUserDetails(String username,
String password,
boolean enabled,
boolean accountNonExpired,
boolean credentialsNonExpired,
boolean accountNonLocked,
Collection<GrantedAuthority> authorities,
long id,
String displayName) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities, id)
this.name = displayName
}
}
You then need to create a custom implementation of UserDetailsService which returns instances of the class above
class UserDetailsService implements GrailsUserDetailsService {
/**
* Some Spring Security classes (e.g. RoleHierarchyVoter) expect at least one role, so
* we give a user with no granted roles this one which gets past that restriction but
* doesn't grant anything.
*/
static final List NO_ROLES = [new GrantedAuthorityImpl(SpringSecurityUtils.NO_ROLE)]
UserDetails loadUserByUsername(String username, boolean loadRoles) {
return loadUserByUsername(username)
}
UserDetails loadUserByUsername(String username) {
User.withTransaction { status ->
User user = User.findByUsername(username)
if (!user) {
throw new UsernameNotFoundException('User not found', username)
}
def authorities = user.authorities.collect {new GrantedAuthorityImpl(it.authority)}
return new CustomUserDetails(
user.username,
user.password,
user.enabled,
!user.accountExpired,
!user.passwordExpired,
!user.accountLocked,
authorities ?: NO_ROLES,
user.id,
user.name)
}
}
}
You need to register an instance of this class as a Spring bean named userDetailsService. I did this by adding the following to Resources.groovy
userDetailsService(UserDetailsService)

Where to check user email does not already exist?

I have an account object that creates a user like so;
public class Account
{
public ICollection<User> Users { get; set; }
public User CreateUser(string email)
{
User user = new User(email);
user.Account = this;
Users.Add(user);
}
}
In my service layer when creating a new user I call this method. However there is a rule that the users email MUST be unique to the account, so where does this go? To me it should go in the CreateUser method with an extra line that just checks that the email is unique to the account.
However if it were to do this then ALL the users for the account would need to be loaded in and that seems like a bit of an overhead to me. It would be better to query the database for the users email - but doing that in the method would require a repository in the account object wouldn't it? Maybe the answer then is when loading the account from the repository instead of doing;
var accountRepository.Get(12);
//instead do
var accountRepository.GetWithUserLoadedOnEmail(12, "someone#example.com");
Then the account object could still check the Users collection for the email and it would have been eagerly loaded in if found.
Does this work? What would you do?
I'm using NHibernate as an ORM.
First off, I do not think you should use exceptions to handle "normal" business logic like checking for duplicate email addresses. This is a well document anti-pattern and is best avoided. Keep the constraint on the DB and handle any duplicate exceptions because they cannot be avoid, but try to keep them to a minimum by checking. I would not recommend locking the table.
Secondly, you've put the DDD tag on this questions, so I'll answer it in a DDD way. It looks to me like you need a domain service or factory. Once you have moved this code in a domain service or factory, you can then inject a UserRepository into it and make a call to it to see if a user already exists with that email address.
Something like this:
public class CreateUserService
{
private readonly IUserRepository userRepository;
public CreateUserService(IUserRepository userRepository)
{
this.userRepository = userRepository;
}
public bool CreateUser(Account account, string emailAddress)
{
// Check if there is already a user with this email address
User userWithSameEmailAddress = userRepository.GetUserByEmailAddress(emailAddress);
if (userWithSameEmailAddress != null)
{
return false;
}
// Create the new user, depending on you aggregates this could be a factory method on Account
User newUser = new User(emailAddress);
account.AddUser(newUser);
return true;
}
}
This allows you to separate the responsiblities a little and use the domain service to coordinate things. Hope that helps!
If you have properly specified the constraints on the users table, the add should throw an exception telling you that there is already a duplicate value. You can either catch that exception in the CreateUser method and return null or some duplicate user status code, or let it flow out and catch it later.
You don't want to test if it exists in your code and then add, because there is a slight possibility that between the test and the add, someone will come along and add the same email with would cause the exception to be thrown anyway...
public User CreateUser(string email)
{
try
{
User user = new User(email);
user.Account = this;
user.Insert();
catch (SqlException e)
{
// It would be best to check for the exception code from your db...
return null;
}
}
Given that "the rule that the users email MUST be unique to the account", then the most important thing is to specify in the database schema that the email is unique, so that the database INSERT will fail if the email is duplicate.
You probably can't prevent two users adding the same email nearly-simultaneously, so the next thing is that the code should handle (gracefully) an INSERT failure cause by the above.
After you've implemented the above, seeing whether the email is unique before you do the insert is just optional.

Resources