How should I structure my application when resources need to interact?

Back again as I plug away on my Ash project!

This is a question about conventions when it comes to structuring actions inside of resources that need to interact with each other. I know how all the logic for these actions need to be coded. I want to make sure I’m structuring this code in a sane manner that follows conventions for how Ash may typically be used.

I’m building out a system that has an Accounts domain, which contains a User resource and a ProviderIdentity resource. The ProviderIdentity resource stores information on third-party authentication identities, such as Google OAuth, which is associated with the User resource. The User resource has many provider identities and the ProviderIdentity belongs to the User resource. For standard authentication, the password hash and email are stored on the User resource itself.

There are two main scenarios that can occur with this particular set up:

  1. The user is signing up for the website for the first time using Google OAuth as the method to create their account. In this scenario, a User would need to be created using the information provided by Google (name, email, etc.) and then a ProviderIdentity created and associated with it.
  2. The user has already created an account using email and password, but they have now decided to log in with Google. Assuming they have verified their email address in our system, we can associate the ProviderIdentity with the User if the emails match. In this scenario, I wouldn’t want the information coming from Google (name, etc.) to overwrite anything already existing on the User resource.
  3. The third scenario, which is a bit of an odd one, which is if they have already created an account using Google but haven’t set a password in their settings. If they try to log in using email and password, the User resource does exist that has this email address, but there is no password associated with it so it doesn’t allow the log in to occur. The same would be if they tried to sign up for a new account using that email. The account already exists. The only way to realtistically allow them to set a password would be to email them a password reset link. I think it would be too risky to allow them to set a password through the settings page without first confirming they have control of the email address associated with it, which is why I’d send the link and not allow it through the normal settings page.

Anyway, that was a long winded explanation to get to the actual question: where should all of this logic live? This too also has a few options:

  1. It can all live within actions. If this is the case, I assume there’s a way to do checks to see if things like the User already exists in a create action in the ProviderIdentity resource and then branch depending on if it does or does not exist to either create the User or to just simply create the ProviderIdentity.
  2. I have actions on both resources that are exposed through the domain and then handle all the logic that ties it together in another module, such as a controller or liveview. Using this method I could do Accounts.get_user_by_email to see if the User exists. If not, create the user with Accounts.create_user and then once that is done call into Accounts.create_provider_identity. These calls would all happen within a different module, not within the resources themselves.
  3. So other method? A hybrid of both approaches? Something even better and more clean?

Which of these would be a best practices approach to structuring the application?

My only other question not related to structuring would be, if I wanted to have all of this happen within a transaction, is there anything special I need to do for any/all of these methods?

Yes this logic should pretty much always live inside of an action :slight_smile:

Now, it doesn’t have to live inside of any particular type of action. For example there are generic actions for encapsulating arbitrary stuff.

1 Like

Awesome, I have a follow up question based on the generic actions you mentioned. Say, using the example above, where I have multiple resources involved such as User, ProviderIdentity, UserToken, or more and there’s an flow that requires the marrying of 3+ resources, would it makes sense to create a resource solely for the purpose of orchestrating multiple resources rather than polluting actions inside of a particular resource with other resources?

An example being registration on the website may require the creation of a user, their credential identities, session tokens, and more in the system. Technically, I could have a register action on the User that calls out to all of these things, but there’s a part of me that feels that that’s overloading the responsibility of the User resource. Instead, would it make sense to create a Registration resource that doesn’t necessarily tie back to the DB, but is there to orchestrate the interaction between all these different resources, which would allow those resources to be focused solely on what they’re scope for (e.g. - creating a user creates a user without having to worry about sessions).

Am I over-engineering by splitting these things in that manner?

I would suggest that it’s totally up to you and there is minimal cost to doing it any which way you please :smiley:

You can have a resource w/ only generic actions for this purpose, you only need to add things like a primary key when there are read/create/update/destroy actions or other attributes.