Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AC-2971] Draft: Policy strategy pattern #47

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

lizard-boy
Copy link

@lizard-boy lizard-boy commented Oct 19, 2024

🎟️ Tracking

TBA

📔 Objective

This is a proof of concept for refactoring the server-side PolicyService.

PolicyService.SaveAsync takes up most of the class, and it has the following problems:

  • Lots of conditional logic and generally a bit hard to follow
  • Bloated, increases in size as we add more policy types
  • Because all policies use the same class, high risk of regression when changing any one policy
  • Requires a high degree of knowledge about all policies, generally has too many responsibilities

This PR suggests implementing a strategy pattern to handle enabling and disabling policies. Each PolicyType should have a corresponding IPolicyStrategy implementation, which can be used to handle actions when the policy is changed from enabled -> disabled or vice versa. PolicyService then injects all strategies and simply delegates the work to the matching strategy.

This also allows for easier unit testing, which can be done for each strategy, rather than the entire PolicyService class.

This PR shows how this could work, using the Single Org policy as an example. Please provide feedback to help us plan future tech debt work.

Future improvements - but out of scope for now

These strategies could also be used for other policy-specific logic, such as:

Separating strategies from the service could also let us have different code ownership, e.g. teams own policies that affect their own domains (something we've talked about for a while now).

📸 Screenshots

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

Greptile Summary

This pull request implements a strategy pattern for policy management in the PolicyService, aiming to improve modularity and reduce complexity.

  • Introduced IPolicyStrategy interface and SingleOrgPolicyStrategy implementation for handling Single Organization policy
  • Refactored PolicyService.SaveAsync to delegate policy handling to appropriate strategies
  • Added PolicyServiceCollectionExtensions for registering policy-related services
  • Modified SharedWeb/Utilities/ServiceCollectionExtensions.cs to include AddPolicyServices() method
  • Simplified PolicyService by removing conditional logic and reducing dependencies

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 file(s) reviewed, 7 comment(s)
Edit PR Review Bot Settings | Greptile

/// <summary>
/// The PolicyType that the strategy is responsible for handling.
/// </summary>
public PolicyType Type { get; }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider making this property read-only to ensure immutability

Comment on lines +55 to +56
var userOrgs = await _organizationUserRepository.GetManyByManyUsersAsync(
removableOrgUsers.Select(ou => ou.UserId!.Value));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: This query might be inefficient for large numbers of users. Consider optimizing or paginating

Comment on lines +63 to +66
// TODO: OrganizationService causes a circular dependency here, we either pass it into all handlers
// TODO: like we do with PolicyService at the moment, or we investigate and break that circle
// await _organizationService.DeleteUserAsync(policy.OrganizationId, orgUser.Id,
// savingUserId);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Circular dependency issue needs to be resolved before this code can be fully implemented

}
}

public async Task HandleDisable(Policy policy, Guid? savingUserId)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: savingUserId parameter is unused in this method. Consider removing if not needed

Comment on lines +76 to +90
var policies = await _policyRepository.GetManyByOrganizationIdAsync(policy.OrganizationId);
if (policies.Any(p => p.Type == PolicyType.RequireSso && p.Enabled))
{
throw new BadRequestException("Single Sign-On Authentication policy is enabled.");
}

if (policies.Any(p => p.Type == PolicyType.MaximumVaultTimeout && p.Enabled))
{
throw new BadRequestException("Maximum Vault Timeout policy is enabled.");
}

if (policies.Any(p => p.Type == PolicyType.ResetPassword && p.Enabled))
{
throw new BadRequestException("Account Recovery policy is enabled.");
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider refactoring repeated policy checks into a separate method for better maintainability

Comment on lines +59 to +61
var currentPolicyIsEnabled =
policy.Id != default &&
(await _policyRepository.GetByIdAsync(policy.Id))?.Enabled == true;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: This logic could be moved to a separate method for better readability


var now = DateTime.UtcNow;
if (policy.Id == default(Guid))
var policyStrategy = _policyStrategies.Single(strategy => strategy.Type == policy.Type);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Handle case where no matching strategy is found

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants