-
Notifications
You must be signed in to change notification settings - Fork 31
Path based Routing for ALB #68
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
resource "aws_lb_listener_rule" "path_based_routing" { | ||
count = var.alb ? length(var.alb_listener_rules) : 0 | ||
|
||
listener_arn = aws_lb_listener.ecs_https[0].arn | ||
priority = var.alb_listener_rules[count.index].priority | ||
|
||
action { | ||
type = "forward" | ||
target_group_arn = var.alb_listener_rules[count.index].target_group_arn | ||
} | ||
|
||
condition { | ||
path_pattern { | ||
values = [var.alb_listener_rules[count.index].path_pattern] | ||
} | ||
} | ||
|
||
dynamic "condition" { | ||
for_each = var.alb_listener_rules[count.index].host_header != null ? [1] : [] | ||
|
||
content { | ||
host_header { | ||
values = [var.alb_listener_rules[count.index].host_header] | ||
} | ||
} | ||
} | ||
|
||
tags = merge( | ||
var.tags, | ||
{ | ||
"Terraform" = true | ||
}, | ||
) | ||
} | ||
|
||
resource "aws_lb_listener_rule" "path_based_routing_test" { | ||
count = var.alb && var.alb_test_listener ? length(var.alb_listener_rules) : 0 | ||
|
||
listener_arn = aws_lb_listener.ecs_test_https[0].arn | ||
priority = var.alb_listener_rules[count.index].priority | ||
|
||
action { | ||
type = "forward" | ||
target_group_arn = var.alb_listener_rules[count.index].target_group_arn | ||
} | ||
|
||
condition { | ||
path_pattern { | ||
values = [var.alb_listener_rules[count.index].path_pattern] | ||
} | ||
} | ||
|
||
dynamic "condition" { | ||
for_each = var.alb_listener_rules[count.index].host_header != null ? [1] : [] | ||
|
||
content { | ||
host_header { | ||
values = [var.alb_listener_rules[count.index].host_header] | ||
} | ||
} | ||
} | ||
|
||
tags = merge( | ||
var.tags, | ||
{ | ||
"Terraform" = true | ||
}, | ||
) | ||
} | ||
|
||
resource "aws_lb_listener_rule" "path_based_routing_internal" { | ||
count = var.alb_internal ? length(var.alb_internal_listener_rules) : 0 | ||
|
||
listener_arn = aws_lb_listener.ecs_https_internal[0].arn | ||
priority = var.alb_internal_listener_rules[count.index].priority | ||
|
||
action { | ||
type = "forward" | ||
target_group_arn = var.alb_internal_listener_rules[count.index].target_group_arn | ||
} | ||
|
||
condition { | ||
path_pattern { | ||
values = [var.alb_internal_listener_rules[count.index].path_pattern] | ||
} | ||
} | ||
|
||
dynamic "condition" { | ||
for_each = var.alb_internal_listener_rules[count.index].host_header != null ? [1] : [] | ||
|
||
content { | ||
host_header { | ||
values = [var.alb_internal_listener_rules[count.index].host_header] | ||
} | ||
} | ||
} | ||
|
||
tags = merge( | ||
var.tags, | ||
{ | ||
"Terraform" = true | ||
}, | ||
) | ||
} | ||
|
||
resource "aws_lb_listener_rule" "path_based_routing_internal_test" { | ||
count = var.alb_internal && var.alb_test_listener ? length(var.alb_internal_listener_rules) : 0 | ||
|
||
listener_arn = aws_lb_listener.ecs_test_https_internal[0].arn | ||
priority = var.alb_internal_listener_rules[count.index].priority | ||
|
||
action { | ||
type = "forward" | ||
target_group_arn = var.alb_internal_listener_rules[count.index].target_group_arn | ||
} | ||
|
||
condition { | ||
path_pattern { | ||
values = [var.alb_internal_listener_rules[count.index].path_pattern] | ||
} | ||
} | ||
|
||
dynamic "condition" { | ||
for_each = var.alb_internal_listener_rules[count.index].host_header != null ? [1] : [] | ||
|
||
content { | ||
host_header { | ||
values = [var.alb_internal_listener_rules[count.index].host_header] | ||
} | ||
} | ||
} | ||
|
||
tags = merge( | ||
var.tags, | ||
{ | ||
"Terraform" = true | ||
}, | ||
) | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,59 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
package test | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"testing" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"github.com/gruntwork-io/terratest/modules/terraform" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"github.com/stretchr/testify/assert" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func TestAlbListenerRules(t *testing.T) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Description: The test function is long and could be split into smaller, more focused functions for better readability and maintainability. Consider extracting the Terraform configuration setup into a separate function. Severity: Low There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The fix addresses the comment by extracting the Terraform configuration setup into a separate function called
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
t.Parallel() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
terraformOptions := &terraform.Options{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// The path to where our Terraform code is located | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TerraformDir: ".", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Variables to pass to our Terraform code using -var options | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Vars: map[string]interface{}{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"name": "test-cluster", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"vpc_id": "vpc-12345678", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"private_subnet_ids": []string{"subnet-1", "subnet-2"}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"public_subnet_ids": []string{"subnet-3", "subnet-4"}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"secure_subnet_ids": []string{"subnet-5", "subnet-6"}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"certificate_arn": "arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"alb": true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"alb_listener_rules": []map[string]interface{}{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"path_pattern": "/api/service1/*", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"target_group_arn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/service1/abcdef1234567890", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"priority": 100, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"path_pattern": "/api/service2/*", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"target_group_arn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/service2/abcdef1234567890", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"priority": 110, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"host_header": "example.com", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Variables to pass to our Terraform code using -var-file options | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
VarFiles: []string{}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Disable colors in Terraform commands so its easier to parse stdout/stderr | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
NoColor: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// At the end of the test, run `terraform destroy` to clean up any resources that were created | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
defer terraform.Destroy(t, terraformOptions) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// This will run `terraform init` and `terraform apply` and fail the test if there are any errors | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
terraform.InitAndApply(t, terraformOptions) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Run `terraform output` to get the values of output variables | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
albPathBasedRoutingRules := terraform.OutputList(t, terraformOptions, "alb_path_based_routing_rules") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Verify we have the correct number of rules | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
assert.Equal(t, 2, len(albPathBasedRoutingRules)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,77 @@ | ||||||||||||||||||||
# Example of using path-based routing with multiple services | ||||||||||||||||||||
|
||||||||||||||||||||
# First service - WordPress | ||||||||||||||||||||
module "ecs_app_wordpress_01" { | ||||||||||||||||||||
source = "git::https://github.com/DNXLabs/terraform-aws-ecs-app.git?ref=1.5.0" | ||||||||||||||||||||
vpc_id = local.workspace["vpc_id"] | ||||||||||||||||||||
cluster_name = module.ecs_apps.ecs_name | ||||||||||||||||||||
service_role_arn = module.ecs_apps.ecs_service_iam_role_arn | ||||||||||||||||||||
task_role_arn = module.ecs_apps.ecs_task_iam_role_arn | ||||||||||||||||||||
alb_listener_https_arn = element(module.ecs_apps.alb_listener_https_arn, 0) | ||||||||||||||||||||
alb_dns_name = element(module.ecs_apps.alb_dns_name, 0) | ||||||||||||||||||||
name = "wordpress-01" | ||||||||||||||||||||
image = "nginxdemos/hello:latest" | ||||||||||||||||||||
container_port = 80 | ||||||||||||||||||||
hostname = "wp01.labs.dnx.host" | ||||||||||||||||||||
hostname_blue = "wp01-blue.labs.dnx.host" | ||||||||||||||||||||
hostname_origin = "wp01-origin.labs.dnx.host" | ||||||||||||||||||||
hosted_zone = "labs.dnx.host" | ||||||||||||||||||||
certificate_arn = local.workspace["cf_certificate_arn"] | ||||||||||||||||||||
healthcheck_path = "/readme.html" | ||||||||||||||||||||
service_health_check_grace_period_seconds = 120 | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
# Second service - API | ||||||||||||||||||||
module "ecs_app_api" { | ||||||||||||||||||||
source = "git::https://github.com/DNXLabs/terraform-aws-ecs-app.git?ref=1.5.0" | ||||||||||||||||||||
vpc_id = local.workspace["vpc_id"] | ||||||||||||||||||||
cluster_name = module.ecs_apps.ecs_name | ||||||||||||||||||||
service_role_arn = module.ecs_apps.ecs_service_iam_role_arn | ||||||||||||||||||||
task_role_arn = module.ecs_apps.ecs_task_iam_role_arn | ||||||||||||||||||||
alb_listener_https_arn = element(module.ecs_apps.alb_listener_https_arn, 0) | ||||||||||||||||||||
alb_dns_name = element(module.ecs_apps.alb_dns_name, 0) | ||||||||||||||||||||
name = "api" | ||||||||||||||||||||
image = "nginxdemos/hello:latest" | ||||||||||||||||||||
container_port = 80 | ||||||||||||||||||||
hostname = "api.labs.dnx.host" | ||||||||||||||||||||
hostname_blue = "api-blue.labs.dnx.host" | ||||||||||||||||||||
hostname_origin = "api-origin.labs.dnx.host" | ||||||||||||||||||||
hosted_zone = "labs.dnx.host" | ||||||||||||||||||||
certificate_arn = local.workspace["cf_certificate_arn"] | ||||||||||||||||||||
healthcheck_path = "/health" | ||||||||||||||||||||
service_health_check_grace_period_seconds = 120 | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
# Configure path-based routing in the ECS cluster module | ||||||||||||||||||||
locals { | ||||||||||||||||||||
path_based_routing_rules = [ | ||||||||||||||||||||
{ | ||||||||||||||||||||
path_pattern = "/wordpress/*" | ||||||||||||||||||||
target_group_arn = module.ecs_app_wordpress_01.target_group_arn | ||||||||||||||||||||
priority = 100 | ||||||||||||||||||||
}, | ||||||||||||||||||||
{ | ||||||||||||||||||||
path_pattern = "/api/*" | ||||||||||||||||||||
target_group_arn = module.ecs_app_api.target_group_arn | ||||||||||||||||||||
priority = 110 | ||||||||||||||||||||
} | ||||||||||||||||||||
] | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
# Update the ECS cluster module to use path-based routing | ||||||||||||||||||||
module "ecs_apps" { | ||||||||||||||||||||
source = "git::https://github.com/DNXLabs/terraform-aws-ecs.git?ref=0.2.0" | ||||||||||||||||||||
name = local.workspace["cluster_name"] | ||||||||||||||||||||
instance_type_1 = "t3.large" | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Description: The ECS cluster module uses outdated instance types, which may not provide optimal performance. Consider updating instance types to more recent and cost-effective options, such as t3.large, t3a.large, or m5.large. Severity: Medium There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The fix updates the instance types used in the ECS cluster module to more recent and cost-effective options. Specifically, it changes instance_type_2 from "t2.large" to "t3a.large" and instance_type_3 from "m2.xlarge" to "m5.large". This addresses the comment by using more up-to-date instance types that can provide better performance and potentially lower costs.
Suggested change
|
||||||||||||||||||||
instance_type_2 = "t2.large" | ||||||||||||||||||||
instance_type_3 = "m2.xlarge" | ||||||||||||||||||||
vpc_id = local.workspace["vpc_id"] | ||||||||||||||||||||
private_subnet_ids = [split(",", local.workspace["private_subnet_ids"])] | ||||||||||||||||||||
public_subnet_ids = [split(",", local.workspace["public_subnet_ids"])] | ||||||||||||||||||||
secure_subnet_ids = [split(",", local.workspace["secure_subnet_ids"])] | ||||||||||||||||||||
certificate_arn = local.workspace["alb_certificate_arn"] | ||||||||||||||||||||
on_demand_percentage = 0 | ||||||||||||||||||||
|
||||||||||||||||||||
# Path-based routing configuration | ||||||||||||||||||||
alb_listener_rules = local.path_based_routing_rules | ||||||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Description: Repetitive code structure across multiple resources. Consider using a module or local to reduce repetition.
Severity: Medium
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fix addresses the repetitive code structure by introducing a local variable
listener_rule_config
that consolidates the configuration for all four listener rules. It then uses a singleaws_lb_listener_rule
resource with afor_each
loop to create the rules based on this configuration. This approach significantly reduces code duplication and improves maintainability.