11package sample .com .constructs ;
22
3+ import software .amazon .awscdk .Fn ;
34import software .amazon .awscdk .RemovalPolicy ;
45import software .amazon .awscdk .services .ec2 .IVpc ;
56import software .amazon .awscdk .services .ec2 .SubnetSelection ;
67import software .amazon .awscdk .services .ec2 .SubnetType ;
7- import software .amazon .awscdk .services .ecr .Repository ;
88import software .amazon .awscdk .services .ecs .CfnExpressGatewayService ;
9+ import software .amazon .awscdk .services .ecs .Cluster ;
910import software .amazon .awscdk .services .iam .ManagedPolicy ;
1011import software .amazon .awscdk .services .iam .Role ;
12+ import software .amazon .awscdk .services .logs .LogGroup ;
1113import software .constructs .Construct ;
14+ import software .constructs .IDependable ;
1215
1316import java .util .List ;
14- import java .util .Map ;
1517
1618/**
1719 * ECS Express Mode service construct for Spring AI agents.
18- * Creates an ECR repository and ECS Express Gateway Service with ALB.
20+ * Creates ECS cluster and ECS Express Gateway Service with ALB.
21+ * ECR repos are created via create-on-push (EcrRegistry construct).
1922 * Reuses Unicorn's ECS roles and adds Bedrock access for AI capabilities.
2023 */
2124public class EcsExpressService extends Construct {
2225
23- private final Repository ecrRepository ;
26+ private final Cluster ecsCluster ;
2427 private final CfnExpressGatewayService expressService ;
2528
2629 public EcsExpressService (final Construct scope , final String id , final EcsExpressServiceProps props ) {
2730 super (scope , id );
2831
2932 String appName = props .getAppName ();
3033
31- // Create ECR Repository
32- this .ecrRepository = Repository .Builder .create (this , "EcrRepository" )
33- .repositoryName (appName )
34- .imageScanOnPush (false )
34+ // Build ECR image URI (repos created via create-on-push)
35+ String imageUri = Fn .sub ("${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/" + appName + ":latest" );
36+
37+ // Create ECS Cluster
38+ this .ecsCluster = Cluster .Builder .create (this , "EcsCluster" )
39+ .clusterName (appName )
40+ .vpc (props .getVpc ())
41+ .build ();
42+
43+ // Create CloudWatch Log Group
44+ LogGroup logGroup = LogGroup .Builder .create (this , "LogGroup" )
45+ .logGroupName ("/aws/ecs/" + appName )
3546 .removalPolicy (RemovalPolicy .DESTROY )
36- .emptyOnDelete (true )
3747 .build ();
3848
3949 // Add Bedrock access to task role for AI capabilities
4050 Role taskRole = props .getUnicorn ().getEcsTaskRole ();
4151 taskRole .addManagedPolicy (ManagedPolicy .fromAwsManagedPolicyName ("AmazonBedrockFullAccess" ));
4252
43- // Get private subnets for the service
44- List <String > privateSubnetIds = props .getVpc ().selectSubnets (SubnetSelection .builder ()
45- .subnetType (SubnetType .PRIVATE_WITH_EGRESS )
53+ // Get PUBLIC subnets for the service (Express Mode uses public subnets with ALB)
54+ List <String > publicSubnetIds = props .getVpc ().selectSubnets (SubnetSelection .builder ()
55+ .subnetType (SubnetType .PUBLIC )
4656 .build ()).getSubnetIds ();
4757
58+ // Get DB security group ID for network access
59+ String dbSecurityGroupId = props .getDatabase ().getDatabaseSecurityGroup ().getSecurityGroupId ();
60+
4861 // Create ECS Express Gateway Service
4962 this .expressService = CfnExpressGatewayService .Builder .create (this , "ExpressService" )
5063 .serviceName (appName )
64+ .cluster (ecsCluster .getClusterName ())
5165 .infrastructureRoleArn (props .getUnicorn ().getEcsInfrastructureRole ().getRoleArn ())
5266 .executionRoleArn (props .getUnicorn ().getEcsTaskExecutionRole ().getRoleArn ())
5367 .taskRoleArn (taskRole .getRoleArn ())
5468 .primaryContainer (CfnExpressGatewayService .ExpressGatewayContainerProperty .builder ()
55- .image (ecrRepository . getRepositoryUri () + ":latest" )
69+ .image (imageUri )
5670 .containerPort (8080 )
71+ .awsLogsConfiguration (CfnExpressGatewayService .ExpressGatewayServiceAwsLogsConfigurationProperty .builder ()
72+ .logGroup (logGroup .getLogGroupName ())
73+ .logStreamPrefix ("ecs" )
74+ .build ())
5775 .secrets (List .of (
5876 CfnExpressGatewayService .SecretProperty .builder ()
5977 .name ("SPRING_DATASOURCE_URL" )
6078 .valueFrom (props .getDatabase ().getParamDBConnectionString ().getParameterArn ())
6179 .build (),
80+ CfnExpressGatewayService .SecretProperty .builder ()
81+ .name ("SPRING_DATASOURCE_USERNAME" )
82+ .valueFrom (props .getDatabase ().getDatabaseSecret ().getSecretArn () + ":username::" )
83+ .build (),
6284 CfnExpressGatewayService .SecretProperty .builder ()
6385 .name ("SPRING_DATASOURCE_PASSWORD" )
6486 .valueFrom (props .getDatabase ().getDatabaseSecret ().getSecretArn () + ":password::" )
@@ -69,19 +91,25 @@ public EcsExpressService(final Construct scope, final String id, final EcsExpres
6991 .memory ("2048" )
7092 .healthCheckPath ("/actuator/health" )
7193 .networkConfiguration (CfnExpressGatewayService .ExpressGatewayServiceNetworkConfigurationProperty .builder ()
72- .subnets (privateSubnetIds )
94+ .subnets (publicSubnetIds )
95+ .securityGroups (List .of (dbSecurityGroupId ))
7396 .build ())
7497 .scalingTarget (CfnExpressGatewayService .ExpressGatewayScalingTargetProperty .builder ()
7598 .minTaskCount (1 )
7699 .maxTaskCount (4 )
77- .autoScalingMetric ("CPU " )
100+ .autoScalingMetric ("AVERAGE_CPU " )
78101 .autoScalingTargetValue (70 )
79102 .build ())
80103 .build ();
104+
105+ // Add dependency on CodeBuild (image must be pushed before service starts)
106+ if (props .getDependsOn () != null ) {
107+ this .expressService .getNode ().addDependency (props .getDependsOn ());
108+ }
81109 }
82110
83- public Repository getEcrRepository () {
84- return ecrRepository ;
111+ public Cluster getEcsCluster () {
112+ return ecsCluster ;
85113 }
86114
87115 public CfnExpressGatewayService getExpressService () {
@@ -94,59 +122,38 @@ public static class EcsExpressServiceProps {
94122 private final IVpc vpc ;
95123 private final Database database ;
96124 private final Unicorn unicorn ;
125+ private final IDependable dependsOn ;
97126
98127 private EcsExpressServiceProps (Builder builder ) {
99128 this .appName = builder .appName ;
100129 this .vpc = builder .vpc ;
101130 this .database = builder .database ;
102131 this .unicorn = builder .unicorn ;
132+ this .dependsOn = builder .dependsOn ;
103133 }
104134
105135 public static Builder builder () {
106136 return new Builder ();
107137 }
108138
109- public String getAppName () {
110- return appName ;
111- }
112-
113- public IVpc getVpc () {
114- return vpc ;
115- }
116-
117- public Database getDatabase () {
118- return database ;
119- }
120-
121- public Unicorn getUnicorn () {
122- return unicorn ;
123- }
139+ public String getAppName () { return appName ; }
140+ public IVpc getVpc () { return vpc ; }
141+ public Database getDatabase () { return database ; }
142+ public Unicorn getUnicorn () { return unicorn ; }
143+ public IDependable getDependsOn () { return dependsOn ; }
124144
125145 public static class Builder {
126146 private String appName ;
127147 private IVpc vpc ;
128148 private Database database ;
129149 private Unicorn unicorn ;
150+ private IDependable dependsOn ;
130151
131- public Builder appName (String appName ) {
132- this .appName = appName ;
133- return this ;
134- }
135-
136- public Builder vpc (IVpc vpc ) {
137- this .vpc = vpc ;
138- return this ;
139- }
140-
141- public Builder database (Database database ) {
142- this .database = database ;
143- return this ;
144- }
145-
146- public Builder unicorn (Unicorn unicorn ) {
147- this .unicorn = unicorn ;
148- return this ;
149- }
152+ public Builder appName (String appName ) { this .appName = appName ; return this ; }
153+ public Builder vpc (IVpc vpc ) { this .vpc = vpc ; return this ; }
154+ public Builder database (Database database ) { this .database = database ; return this ; }
155+ public Builder unicorn (Unicorn unicorn ) { this .unicorn = unicorn ; return this ; }
156+ public Builder dependsOn (IDependable dependsOn ) { this .dependsOn = dependsOn ; return this ; }
150157
151158 public EcsExpressServiceProps build () {
152159 return new EcsExpressServiceProps (this );
0 commit comments