Skip to content

Commit 911f92c

Browse files
feat!: Single connection to the RabbitMQ broker and required listenReplies prop (#151)
* fix: update async properties handling and improve error messaging * docs: add RabbitMQ documentation * build: update dependencies * feat: customize the RabbitMQ connection --------- Co-authored-by: lugomez <[email protected]>
1 parent 30f901d commit 911f92c

File tree

35 files changed

+674
-165
lines changed

35 files changed

+674
-165
lines changed

async/async-commons/src/main/java/org/reactivecommons/async/commons/DiscardNotifier.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.reactivecommons.async.commons.communications.Message;
44
import reactor.core.publisher.Mono;
55

6+
@FunctionalInterface
67
public interface DiscardNotifier {
78
Mono<Void> notifyDiscard(Message message);
89
}

async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/matcher/Matcher.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.Set;
44

5+
@FunctionalInterface
56
public interface Matcher {
67
String match(Set<String> sources, String target);
78
}

async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/communications/UnroutableMessageNotifierTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ class UnroutableMessageNotifierTest {
4444

4545
@BeforeEach
4646
void setUp() {
47-
// Usar el constructor por defecto y espiar el sink interno
47+
// Use the default constructor and spy on the internal sink
4848
unroutableMessageNotifier = new UnroutableMessageNotifier();
49-
// Inyectar el mock del sink usando un spy para poder verificarlo
49+
// Inject the sink mock using a spy to verify it
5050
try {
5151
java.lang.reflect.Field sinkField = UnroutableMessageNotifier.class.getDeclaredField("sink");
5252
sinkField.setAccessible(true);

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ buildscript {
1313
plugins {
1414
id 'jacoco'
1515
id 'org.sonarqube' version '6.3.1.5724'
16-
id 'org.springframework.boot' version '3.5.5' apply false
16+
id 'org.springframework.boot' version '3.5.6' apply false
1717
id 'io.github.gradle-nexus.publish-plugin' version '2.0.0'
18-
id 'co.com.bancolombia.cleanArchitecture' version '3.25.0'
18+
id 'co.com.bancolombia.cleanArchitecture' version '3.26.1'
1919
}
2020

2121
repositories {

docs/docs/migration-guides.md

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
---
2+
sidebar_position: 4
3+
---
4+
5+
# Migration
6+
7+
## From 5.x.x to 6.x.x
8+
9+
### New Features
10+
11+
- **Connection customization:** You can now customize the RabbitMQ connection by defining a
12+
`ConnectionFactoryCustomizer` bean. For more details,
13+
see [Customizing the connection](/reactive-commons-java/docs/reactive-commons/configuration_properties/rabbitmq#customizing-the-connection).
14+
15+
```java title="Programmatic configuration"
16+
17+
@Bean
18+
public ConnectionFactoryCustomizer connectionFactoryCustomizer() {
19+
return (ConnectionFactoryCustomizer) (asyncProps, connectionFactory) -> {
20+
connectionFactory.setExceptionHandler(new MyCustomExceptionHandler()); // Optional custom exception handler
21+
connectionFactory.setCredentialsProvider(new MyCustomCredentialsProvider()); // Optional custom credentials provider
22+
return connectionFactory;
23+
};
24+
}
25+
```
26+
27+
### Change notes
28+
29+
- The configuration property `listenReplies` for RabbitMQ now defaults to `null`. Previously, it was `true`, causing all
30+
applications to subscribe to a reply queue even when not needed.
31+
- The domain `app` is now **required**. If not defined, the application will fail to start.
32+
33+
### Actions
34+
35+
- If your application uses the ReqReply pattern, you must explicitly set `app.async.app.listenReplies` to `true`.
36+
Otherwise, it should be `false` to avoid unnecessary resource usage:
37+
38+
```yaml title="application.yaml"
39+
app:
40+
async:
41+
app:
42+
listenReplies: true # set to true if ReqReply is required, false if not
43+
```
44+
45+
```java title="Programmatic configuration"
46+
@Configuration
47+
public class MyDomainConfig {
48+
49+
@Bean
50+
@Primary
51+
public AsyncRabbitPropsDomainProperties customDomainProperties() {
52+
RabbitProperties propertiesApp = new RabbitProperties();
53+
// Additional connection configuration goes here...
54+
return AsyncRabbitPropsDomainProperties.builder()
55+
.withDomain("app", AsyncProps.builder()
56+
.connectionProperties(propertiesApp)
57+
.listenReplies(Boolean.TRUE) // set to true if ReqReply is required, false if not
58+
.build())
59+
.build();
60+
}
61+
}
62+
```
63+
64+
---
65+
66+
- The domain `app` must be defined in your configuration. Otherwise, the application will throw an exception at startup:
67+
68+
```yaml title="application.yaml"
69+
app:
70+
async:
71+
app: # Configure the 'app' domain
72+
# domain configuration goes here
73+
```
74+
75+
```java title="Programmatic configuration"
76+
@Configuration
77+
public class MyDomainConfig {
78+
79+
@Bean
80+
@Primary
81+
public AsyncRabbitPropsDomainProperties customDomainProperties() {
82+
RabbitProperties propertiesApp = new RabbitProperties();
83+
// Additional connection configuration goes here...
84+
return AsyncRabbitPropsDomainProperties.builder()
85+
.withDomain("app", AsyncProps.builder() // Configure the 'app' domain
86+
.connectionProperties(propertiesApp)
87+
.build())
88+
.build();
89+
}
90+
}
91+
```
92+
93+
## From 4.x.x to 5.x.x
94+
95+
### New Features
96+
97+
- **Support for multiple brokers:** It is now possible to configure and connect to up to two brokers simultaneously,
98+
using
99+
independent domains in the configuration.
100+
101+
### Change notes
102+
103+
- Configuration properties are now defined per domain, allowing each to have its own properties and connection
104+
settings.
105+
- The broker connection is no longer manually defined in the code. It is now automatically managed based on the
106+
configuration declared in the `application.yaml` file or through programmatic configuration.
107+
108+
### Actions
109+
110+
- The `app` domain needs to be defined to specify the configuration properties.
111+
112+
Before:
113+
114+
```yaml title="application.yaml"
115+
app:
116+
async:
117+
withDLQRetry: true
118+
maxRetries: 1
119+
retryDelay: 1000
120+
```
121+
122+
Now:
123+
124+
```yaml title="application.yaml"
125+
app:
126+
async:
127+
app: # this is the name of the default domain
128+
withDLQRetry: true
129+
maxRetries: 1
130+
retryDelay: 1000
131+
```
132+
133+
- Migrate the connection configuration:
134+
135+
Before: the connection was defined manually in a Java class, as shown below:
136+
137+
```java
138+
@Log4j2
139+
@Configuration
140+
@RequiredArgsConstructor
141+
public class MyDomainConfig {
142+
143+
private final RabbitMQConnectionProperties properties;
144+
private static final String TLS = "TLSv1.2";
145+
private static final String FAIL_MSG = "Error creating ConnectionFactoryProvider in enroll";
146+
147+
@Primary
148+
@Bean
149+
public ConnectionFactoryProvider getConnectionFactoryProvider() {
150+
final var factory = new ConnectionFactory();
151+
var map = PropertyMapper.get();
152+
map.from(properties::hostname).whenNonNull().to(factory::setHost);
153+
map.from(properties::port).to(factory::setPort);
154+
map.from(properties::username).whenNonNull().to(factory::setUsername);
155+
map.from(properties::password).whenNonNull().to(factory::setPassword);
156+
map.from(properties::ssl).whenTrue().as(isSsl -> factory).to(this::configureSsl);
157+
return () -> factory;
158+
}
159+
160+
private void configureSsl(ConnectionFactory factory) {
161+
try {
162+
var sslContext = SSLContext.getInstance(TLS);
163+
var trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
164+
trustManagerFactory.init((KeyStore) null);
165+
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
166+
factory.useSslProtocol(sslContext);
167+
} catch (KeyManagementException | KeyStoreException | NoSuchAlgorithmException e) {
168+
log.error("{}: {}", FAIL_MSG, e);
169+
}
170+
}
171+
}
172+
```
173+
174+
Now: the connection is configured directly in the `application.yaml` file per domain:
175+
176+
```yaml title="application.yaml"
177+
app:
178+
async:
179+
app: # this is the name of the default domain
180+
connectionProperties: # you can override the connection properties of each domain
181+
host: localhost
182+
port: 5672
183+
username: guest
184+
password: guest
185+
virtual-host: /
186+
# Another domain can be configured with same properties structure that app
187+
accounts: # this is a second domain name and can have another independent setup
188+
connectionProperties: # you can override the connection properties of each domain
189+
host: localhost
190+
port: 5672
191+
username: guest
192+
password: guest
193+
virtual-host: /accounts
194+
```
195+
196+
Domains can also be configured programmatically:
197+
198+
```java title="Programmatic configuration"
199+
@Configuration
200+
public class MyDomainConfig {
201+
202+
@Bean
203+
@Primary
204+
public AsyncRabbitPropsDomainProperties customDomainProperties() {
205+
RabbitProperties propertiesApp = new RabbitProperties();
206+
propertiesApp.setHost("localhost");
207+
propertiesApp.setPort(5672);
208+
propertiesApp.setVirtualHost("/");
209+
propertiesApp.setUsername("guest");
210+
propertiesApp.setPassword("guest");
211+
212+
RabbitProperties propertiesAccounts = new RabbitProperties();
213+
propertiesAccounts.setHost("localhost");
214+
propertiesAccounts.setPort(5672);
215+
propertiesAccounts.setVirtualHost("/accounts");
216+
propertiesAccounts.setUsername("guest");
217+
propertiesAccounts.setPassword("guest");
218+
219+
return AsyncRabbitPropsDomainProperties.builder()
220+
.withDomain("app", AsyncProps.builder()
221+
.connectionProperties(propertiesApp)
222+
.build())
223+
.withDomain("accounts", AsyncProps.builder()
224+
.connectionProperties(propertiesAccounts)
225+
.build())
226+
.build();
227+
}
228+
}
229+
```

docs/docs/reactive-commons/1-getting-started.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Commons.
1717

1818
You need Java JRE installed (Java 17 or later).
1919

20-
You also need to install RabbitMQ. Follow the [instructions from the website](https://www.rabbitmq.com/download.html)
20+
You also need to install RabbitMQ. Follow the [instructions from the website](https://www.rabbitmq.com/download.html).
2121

2222
## Start RabbitMQ
2323

@@ -50,15 +50,16 @@ dependencies {
5050
}
5151
```
5252

53-
Note: If you will use Cloud Events, you should include the Cloud Events dependency:
53+
:::tip
54+
If you will use Cloud Events, you should include the Cloud Events dependency:
5455

5556
```groovy
5657
dependencies {
5758
implementation 'io.cloudevents:cloudevents-json-jackson:4.0.1'
5859
}
5960
```
6061

61-
```groovy
62+
:::
6263

6364
### Configuration properties
6465

docs/docs/reactive-commons/3-sending-a-command.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
sidebar_position: 2
2+
sidebar_position: 3
33
---
44

55
# Sending a Command
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
{
22
"label": "Reactive Commons",
33
"position": 2,
4+
"collapsed": false,
45
"link": {
56
"type": "generated-index",
67
"description": "Learn how to build reactive systems using the Reactive Commons library."
78
}
8-
}
9+
}

0 commit comments

Comments
 (0)