Skip to content

Commit a9a6f82

Browse files
committed
Add EventBusInterfaceExtension and enhance SQS templates for message handling
1 parent 1ca805a commit a9a6f82

File tree

7 files changed

+256
-7
lines changed

7 files changed

+256
-7
lines changed

Modules/Intent.Modules.Aws.Lambda.Functions.Sqs/Templates/LambdaFunctionConsumer/LambdaFunctionConsumerTemplatePartial.cs

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
1-
using System;
2-
using System.Collections.Generic;
1+
using System.Linq;
32
using Intent.Engine;
43
using Intent.Modelers.Services.EventInteractions;
4+
using Intent.Modules.Aws.Sqs.Templates;
55
using Intent.Modules.Common;
66
using Intent.Modules.Common.CSharp.Builder;
77
using Intent.Modules.Common.CSharp.Templates;
88
using Intent.Modules.Common.Templates;
9+
using Intent.Modules.Common.UnitOfWork.Shared;
10+
using Intent.Modules.Eventing.Contracts.Templates;
911
using Intent.RoslynWeaver.Attributes;
1012
using Intent.Templates;
1113

14+
using LambdaNugetPackages = Intent.Modules.Aws.Lambda.Functions.NugetPackages;
15+
using SqsNugetPackages = Intent.Modules.Aws.Sqs.NugetPackages;
16+
1217
[assembly: DefaultIntentManaged(Mode.Fully)]
1318
[assembly: IntentTemplate("Intent.ModuleBuilder.CSharp.Templates.CSharpTemplatePartial", Version = "1.0")]
1419

@@ -22,14 +27,65 @@ public partial class LambdaFunctionConsumerTemplate : CSharpTemplateBase<Integra
2227
[IntentManaged(Mode.Fully, Body = Mode.Ignore)]
2328
public LambdaFunctionConsumerTemplate(IOutputTarget outputTarget, IntegrationEventHandlerModel model) : base(TemplateId, outputTarget, model)
2429
{
30+
AddNugetDependency(LambdaNugetPackages.AmazonLambdaCore(OutputTarget));
31+
AddNugetDependency(LambdaNugetPackages.AmazonLambdaAnnotations(OutputTarget));
32+
AddNugetDependency(SqsNugetPackages.AmazonLambdaSqsEvents(OutputTarget));
33+
2534
CSharpFile = new CSharpFile(this.GetNamespace(), this.GetFolderPath())
26-
.AddClass($"{Model.Name}", @class =>
35+
.AddUsing("System")
36+
.AddUsing("System.Collections.Generic")
37+
.AddUsing("System.Threading")
38+
.AddUsing("System.Threading.Tasks")
39+
.AddUsing("Amazon.Lambda.Annotations")
40+
.AddUsing("Amazon.Lambda.Core")
41+
.AddUsing("Amazon.Lambda.SQSEvents")
42+
.AddUsing("Microsoft.Extensions.Logging")
43+
.AddClass(GetConsumerName(), @class =>
2744
{
45+
@class.RepresentsModel(Model);
46+
2847
@class.AddConstructor(ctor =>
2948
{
30-
ctor.AddParameter("string", "exampleParam", param =>
49+
ctor.AddParameter($"ILogger<{@class.Name}>", "logger", param => param.IntroduceReadonlyField((_, statement) => statement.ThrowArgumentNullException()));
50+
ctor.AddParameter(this.GetSqsMessageDispatcherInterfaceName(), "dispatcher", param => param.IntroduceReadonlyField((_, statement) => statement.ThrowArgumentNullException()));
51+
ctor.AddParameter(this.GetEventBusInterfaceName(), "eventBus", param => param.IntroduceReadonlyField((_, statement) => statement.ThrowArgumentNullException()));
52+
ctor.AddParameter("IServiceProvider", "serviceProvider", param => param.IntroduceReadonlyField((_, statement) => statement.ThrowArgumentNullException()));
53+
});
54+
55+
@class.AddMethod("Task", "ProcessAsync", method =>
56+
{
57+
method.Async();
58+
method.AddAttribute("LambdaFunction");
59+
method.AddParameter(UseType("Amazon.Lambda.SQSEvents.SQSEvent"), "sqsEvent");
60+
method.AddParameter(UseType("Amazon.Lambda.Core.ILambdaContext"), "context");
61+
62+
method.AddStatement("// AWSLambda0107: passing CancellationToken parameters is not supported; use CancellationToken.None instead.");
63+
method.AddStatement("var cancellationToken = CancellationToken.None;");
64+
65+
method.AddForEachStatement("record", "sqsEvent.Records", loop =>
3166
{
32-
param.IntroduceReadonlyField();
67+
loop.AddTryBlock(tryBlock =>
68+
{
69+
var dispatch = new CSharpAwaitExpression(new CSharpInvocationStatement("_dispatcher", "DispatchAsync")
70+
.AddArgument("_serviceProvider")
71+
.AddArgument("record")
72+
.AddArgument("cancellationToken"));
73+
dispatch.AddMetadata("service-dispatch-statement", true);
74+
75+
tryBlock.ApplyUnitOfWorkImplementations(
76+
template: this,
77+
constructor: @class.Constructors.First(),
78+
invocationStatement: dispatch,
79+
cancellationTokenExpression: "cancellationToken");
80+
81+
tryBlock.AddStatement("await _eventBus.FlushAllAsync(cancellationToken);");
82+
});
83+
84+
loop.AddCatchBlock(UseType("System.Exception"), "ex", catchBlock =>
85+
{
86+
catchBlock.AddStatement($"_logger.LogError(ex, \"Error processing {GetConsumerName()} message with ID {{MessageId}}\", record.MessageId);");
87+
catchBlock.AddStatement("throw;");
88+
});
3389
});
3490
});
3591
});
@@ -49,5 +105,10 @@ public override string TransformText()
49105
{
50106
return CSharpFile.ToString();
51107
}
108+
109+
private string GetConsumerName()
110+
{
111+
return Model.Name.RemoveSuffix("Handler").EnsureSuffixedWith("Consumer");
112+
}
52113
}
53114
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System.Linq;
2+
using Intent.Engine;
3+
using Intent.Modules.Common;
4+
using Intent.Modules.Common.CSharp.Templates;
5+
using Intent.Modules.Common.Plugins;
6+
using Intent.Modules.Common.Templates;
7+
using Intent.Plugins.FactoryExtensions;
8+
using Intent.RoslynWeaver.Attributes;
9+
10+
[assembly: DefaultIntentManaged(Mode.Fully)]
11+
[assembly: IntentTemplate("Intent.ModuleBuilder.Templates.FactoryExtension", Version = "1.0")]
12+
13+
namespace Intent.Modules.Aws.Sqs.FactoryExtensions
14+
{
15+
[IntentManaged(Mode.Fully, Body = Mode.Merge)]
16+
public class EventBusInterfaceExtension : FactoryExtensionBase
17+
{
18+
public override string Id => "Intent.Aws.Sqs.EventBusInterfaceExtension";
19+
20+
[IntentManaged(Mode.Ignore)]
21+
public override int Order => 0;
22+
23+
protected override void OnAfterTemplateRegistrations(IApplication application)
24+
{
25+
var templates = application.FindTemplateInstances<ICSharpFileBuilderTemplate>(TemplateDependency.OnTemplate("Application.Eventing.EventBusInterface"));
26+
foreach (var template in templates)
27+
{
28+
template.CSharpFile.OnBuild(file =>
29+
{
30+
file.AddUsing("System");
31+
var @interface = file.Interfaces.First();
32+
if (@interface.Methods.Any(x => x.Name == "Send"))
33+
{
34+
return;
35+
}
36+
37+
@interface.AddMethod("void", "Send", m => m
38+
.AddGenericParameter("T")
39+
.AddParameter("T", "message")
40+
.AddGenericTypeConstraint("T", c => c.AddType("class")));
41+
});
42+
}
43+
}
44+
}
45+
}

Modules/Intent.Modules.Aws.Sqs/Intent.Aws.Sqs.imodspec

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@
6666
</template>
6767
</templates>
6868
<decorators></decorators>
69-
<factoryExtensions />
69+
<factoryExtensions>
70+
<factoryExtension id="Intent.Aws.Sqs.EventBusInterfaceExtension" externalReference="0c6e73a6-fb50-4545-83a2-ec6624b3dbb8" />
71+
</factoryExtensions>
7072
<moduleSettings></moduleSettings>
7173
<interoperability>
7274
<detect id="Intent.Aws.Lambda.Functions">
@@ -77,6 +79,7 @@
7779
</interoperability>
7880
<dependencies>
7981
<dependency id="Intent.Aws.Common" version="1.0.4-pre.1" />
82+
<dependency id="Intent.Aws.Lambda.Functions" version="1.0.2-pre.1" />
8083
<dependency id="Intent.Common" version="3.9.1" />
8184
<dependency id="Intent.Common.CSharp" version="3.9.6" />
8285
<dependency id="Intent.Common.Types" version="3.4.0" />
@@ -85,7 +88,6 @@
8588
<dependency id="Intent.Modelers.Services" version="4.0.5" />
8689
<dependency id="Intent.Modelers.Services.EventInteractions" version="1.2.1" />
8790
<dependency id="Intent.OutputManager.RoslynWeaver" version="4.9.10" />
88-
<dependency id="Intent.Aws.Lambda.Functions" version="1.0.2-pre.1" />
8991
</dependencies>
9092
<files>
9193
<file src="$outDir$/$id$.dll" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<class id="0c6e73a6-fb50-4545-83a2-ec6624b3dbb8" type="Factory Extension" typeId="7d008e84-bb28-4b10-ba28-7439202fca76">
3+
<name>EventBusInterfaceExtension</name>
4+
<display>EventBusInterfaceExtension</display>
5+
<isAbstract>false</isAbstract>
6+
<genericTypes />
7+
<isMapped>false</isMapped>
8+
<parentFolderId>259088af-8612-4dd5-9bd7-b673553c3a6c</parentFolderId>
9+
<packageId>259088af-8612-4dd5-9bd7-b673553c3a6c</packageId>
10+
<packageName>Intent.Aws.Sqs</packageName>
11+
<stereotypes />
12+
<metadata />
13+
<childElements />
14+
</class>

Modules/Intent.Modules.Aws.Sqs/Intent.Modules.Aws.Sqs.application.output.log

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@
2424
<ProjectRelativeFilePath>MessageModelStereotypeExtensions.cs</ProjectRelativeFilePath>
2525
<IsIgnored>false</IsIgnored>
2626
</FileLog>
27+
<FileLog>
28+
<ProjectId>0f54df65-887a-4955-a3de-02f6f231d236</ProjectId>
29+
<CorrelationId>Intent.ModuleBuilder.Templates.FactoryExtension#0c6e73a6-fb50-4545-83a2-ec6624b3dbb8</CorrelationId>
30+
<OverwriteBehaviour>always</OverwriteBehaviour>
31+
<ApplicationRelativeFilePath>FactoryExtensions/EventBusInterfaceExtension.cs</ApplicationRelativeFilePath>
32+
<ProjectRelativeFilePath>EventBusInterfaceExtension.cs</ProjectRelativeFilePath>
33+
<IsIgnored>false</IsIgnored>
34+
</FileLog>
2735
<FileLog>
2836
<ProjectId>e6a962cc-6864-45d9-ab6a-fcfb19173e82</ProjectId>
2937
<CorrelationId>Intent.ModuleBuilder.IModSpecFile#7e37b40c-19ea-45f5-9bf2-a2453da12dc2</CorrelationId>

Modules/Intent.Modules.Aws.Sqs/Templates/SqsEventBus/SqsEventBusTemplatePartial.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ public SqsEventBusTemplate(IOutputTarget outputTarget, object model = null) : ba
5959
method.AddStatement("ValidateMessage(message);");
6060
method.AddStatement("_messageQueue.Add(new MessageEntry(message));");
6161
});
62+
63+
@class.AddMethod("void", "Send", method =>
64+
{
65+
method.AddGenericParameter("T", out var T);
66+
method.AddGenericTypeConstraint(T, c => c.AddType("class"));
67+
method.AddParameter(T, "message");
68+
69+
method.AddStatement("ValidateMessage(message);");
70+
method.AddStatement("_messageQueue.Add(new MessageEntry(message));");
71+
});
6272

6373
@class.AddMethod("Task", "FlushAllAsync", method =>
6474
{
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Intent.Aws.Sqs
2+
3+
This module provides patterns for working with AWS SQS directly.
4+
5+
## What is AWS SQS?
6+
7+
Amazon Simple Queue Service (AWS SQS) is a fully managed message queuing service that enables you to decouple and scale microservices, distributed systems, and serverless applications. It provides reliable, scalable, and cost-effective message delivery between independent application components, offering features like message ordering, message deduplication, visibility timeouts, and dead-letter queues.
8+
9+
For more information on AWS SQS, check out their [official docs](https://docs.aws.amazon.com/sqs/).
10+
11+
## Modeling Integration Events and Commands
12+
13+
Modeling Integration Events can be achieved from within the Services designer.
14+
This module automatically installs the `Intent.Modelers.Eventing` module which provides designer modeling capabilities for integration events and commands.
15+
For details on modeling integration events and commands, refer to its [README](https://docs.intentarchitect.com/articles/modules-common/intent-modelers-eventing/intent-modelers-eventing.html).
16+
17+
You can model Integration Events (orange Message envelope element), and it will automatically configure to work against an SQS Queue with a derived name.
18+
However, you can customize the name of the Queue by applying an `AWS SQS` stereotype and setting the `Queue Name` to the desired name.
19+
20+
Similarly, you can model Integration Commands (green Message envelope element), and it will automatically configure to work against an SQS Queue with a derived name.
21+
You can customize the name by applying an `AWS SQS` stereotype and setting the `Queue Name` to the desired name.
22+
23+
## AWS SQS Implementation
24+
25+
Provides an AWS SQS specific implementation of the `IEventBus` interface for dispatching messages.
26+
27+
## Message Publishing
28+
29+
Message publishing can be done through the `IEventBus` interface using the `Publish` method to send messages to SQS queues.
30+
31+
## Message Consumption
32+
33+
For every message subscribed to in the `Services Designer` will receive its own Integration Event handler.
34+
35+
This is what the Business logic Integration Event handler looks like:
36+
37+
```csharp
38+
[IntentManaged(Mode.Merge, Signature = Mode.Fully)]
39+
public class ClientCreatedIntegrationEventHandler : IIntegrationEventHandler<ClientCreatedEvent>
40+
{
41+
[IntentManaged(Mode.Ignore)]
42+
public ClientCreatedIntegrationEventHandler()
43+
{
44+
}
45+
46+
[IntentManaged(Mode.Fully, Body = Mode.Ignore)]
47+
public async Task HandleAsync(ClientCreatedEvent message, CancellationToken cancellationToken = default)
48+
{
49+
// Business logic here
50+
throw new NotImplementedException();
51+
}
52+
}
53+
```
54+
55+
> [!NOTE]
56+
>
57+
> This module will not be generating consumer code automatically for you. Look at the [Related Modules](#related-modules) section to see which modules cause this to happen.
58+
59+
## Configuring SQS
60+
61+
When you're publishing using AWS SQS, you will need to configure it in your `appsettings.json` file.
62+
63+
### AWS Configuration
64+
65+
```json
66+
{
67+
"AWS": {
68+
"Region": "us-east-1"
69+
},
70+
"AwsSqs": {
71+
"ClientCreated": {
72+
"QueueUrl": "https://sqs.us-east-1.amazonaws.com/123456789012/client-created"
73+
},
74+
"OrderPlaced": {
75+
"QueueUrl": "https://sqs.us-east-1.amazonaws.com/123456789012/order-placed"
76+
}
77+
}
78+
}
79+
```
80+
81+
### LocalStack Support
82+
83+
For local development, set the following in your `appsettings.Development.json`:
84+
85+
```json
86+
{
87+
"AWS": {
88+
"ServiceURL": "http://localhost:4566",
89+
"Region": "us-east-1"
90+
}
91+
}
92+
```
93+
94+
The module automatically detects the `ServiceURL` setting and configures the SQS client accordingly for local development with LocalStack.
95+
96+
## Message Attributes
97+
98+
The module automatically sets the `MessageType` attribute on published messages for routing and filtering purposes. This enables consumers to identify the message type without parsing the message body.
99+
100+
## Related Modules
101+
102+
### Intent.Aws.Lambda.Functions.Sqs
103+
104+
This module handles the consumer code for AWS SQS when AWS Lambda is selected as the hosting technology, allowing Lambda functions to process messages from SQS queues.
105+
106+
> [!NOTE]
107+
>
108+
> Currently, this module only supports AWS Lambda Functions as the hosting technology through the `Intent.Aws.Lambda.Functions.Sqs` module. Support for other hosting technologies may be added in future releases. If you need support for a different hosting platform, please reach out to us on [GitHub](https://github.com/IntentArchitect/Support) or email us at [[email protected]](mailto://[email protected]).
109+

0 commit comments

Comments
 (0)