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

feat: Improve local subscriptions #3028

Merged
merged 1 commit into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.quarkus.logging.Log;
import xyz.block.ftl.Export;
import xyz.block.ftl.Subscription;
import xyz.block.ftl.Topic;
import xyz.block.ftl.TopicDefinition;
import xyz.block.ftl.Verb;
Expand All @@ -14,14 +15,19 @@ interface TestTopic extends Topic<PubSubEvent> {

}

@TopicDefinition("localTopic")
interface LocalTopic extends Topic<PubSubEvent> {

}

@Export
@TopicDefinition("topic2")
interface Topic2 extends Topic<PubSubEvent> {

}

@Verb
void publishTen(TestTopic testTopic) throws Exception {
void publishTen(LocalTopic testTopic) throws Exception {
for (var i = 0; i < 10; ++i) {
var t = java.time.ZonedDateTime.now();
Log.infof("Publishing %s", t);
Expand All @@ -42,4 +48,9 @@ void publishOneToTopic2(Topic2 topic2) throws Exception {
Log.infof("Publishing %s", t);
topic2.publish(new PubSubEvent().setTime(t));
}

@Subscription(topicClass = LocalTopic.class, name = "localSubscription")
public void local(TestTopic testTopic, PubSubEvent event) {
testTopic.publish(event);
}
}
5 changes: 2 additions & 3 deletions docs/content/docs/reference/pubsub.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,13 @@ There are two ways to subscribe to a topic. The first is to declare a method wit
subscribing to a topic inside the same module:

```java
@Subscription(topic = "invoices", name = "invoicesSubscription")
@Subscription(topicClass = InvoiceTopic.class, name = "invoicesSubscription")
public void consumeInvoice(Invoice event) {
// ...
}
```

This is ok, but it requires the use of string constants for the topic name, which can be error-prone. If you are subscribing to a topic from
another module, FTL will generate a type-safe subscription meta annotation you can use to subscribe to the topic:
If you are subscribing to a topic from another module, FTL will generate a type-safe subscription meta annotation you can use to subscribe to the topic:

```java
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import xyz.block.ftl.LeaseClient;
import xyz.block.ftl.Secret;
import xyz.block.ftl.Subscription;
import xyz.block.ftl.TopicDefinition;
import xyz.block.ftl.TypeAlias;
import xyz.block.ftl.TypeAliasMapper;
import xyz.block.ftl.Verb;
Expand All @@ -33,4 +34,5 @@ private FTLDotNames() {
public static final DotName SUBSCRIPTION = DotName.createSimple(Subscription.class);
public static final DotName LEASE_CLIENT = DotName.createSimple(LeaseClient.class);
public static final DotName GENERATED_REF = DotName.createSimple(GeneratedRef.class);
public static final DotName TOPIC_DEFINITION = DotName.createSimple(TopicDefinition.class);
}
Original file line number Diff line number Diff line change
Expand Up @@ -620,10 +620,10 @@ private boolean setDeclExport(String name, boolean export) {
var existing = decls.get(name);
if (existing != null) {
if (existing.hasData()) {
var merged = existing.getData().toBuilder().setExport(export).build();
var merged = existing.getData().toBuilder().setExport(export || existing.getData().getExport()).build();
decls.put(name, Decl.newBuilder().setData(merged).build());
} else if (existing.hasTypeAlias()) {
var merged = existing.getTypeAlias().toBuilder().setExport(export).build();
var merged = existing.getTypeAlias().toBuilder().setExport(export || existing.getData().getExport()).build();
decls.put(name, Decl.newBuilder().setTypeAlias(merged).build());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;

import io.quarkus.builder.item.SimpleBuildItem;
import xyz.block.ftl.Topic;

public final class SubscriptionMetaAnnotationsBuildItem extends SimpleBuildItem {

Expand All @@ -23,13 +25,33 @@ public Map<DotName, SubscriptionAnnotation> getAnnotations() {
public record SubscriptionAnnotation(String module, String topic, String name) {
}

public static SubscriptionAnnotation fromJandex(AnnotationInstance subscriptions, String currentModuleName) {
AnnotationValue moduleValue = subscriptions.value("module");
public static SubscriptionAnnotation fromJandex(IndexView indexView, AnnotationInstance subscriptions,
String currentModuleName) {

AnnotationValue moduleValue = subscriptions.value("module");
AnnotationValue topicValue = subscriptions.value("topic");
AnnotationValue topicClassValue = subscriptions.value("topicClass");
String topicName;
if (topicValue != null && !topicValue.asString().isEmpty()) {
if (topicClassValue != null && !topicClassValue.asClass().name().toString().equals(Topic.class.getName())) {
throw new IllegalArgumentException("Cannot specify both topic and topicClass");
}
topicName = topicValue.asString();
} else if (topicClassValue != null && !topicClassValue.asClass().name().toString().equals(Topic.class.getName())) {
var topicClass = indexView.getClassByName(topicClassValue.asClass().name());
AnnotationInstance annotation = topicClass.annotation(FTLDotNames.TOPIC_DEFINITION);
if (annotation == null) {
throw new IllegalArgumentException(
"topicClass must be annotated with @TopicDefinition for subscription " + subscriptions);
}
topicName = annotation.value().asString();
} else {
throw new IllegalArgumentException("Either topic or topicClass must be specified on " + subscriptions);
}
return new SubscriptionMetaAnnotationsBuildItem.SubscriptionAnnotation(
moduleValue == null || moduleValue.asString().isEmpty() ? currentModuleName
: moduleValue.asString(),
subscriptions.value("topic").asString(),
topicName,
subscriptions.value("name").asString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ SubscriptionMetaAnnotationsBuildItem subscriptionAnnotations(CombinedIndexBuildI
continue;
}
annotations.put(subscriptions.target().asClass().name(),
SubscriptionMetaAnnotationsBuildItem.fromJandex(subscriptions, moduleNameBuildItem.getModuleName()));
SubscriptionMetaAnnotationsBuildItem.fromJandex(combinedIndexBuildItem.getComputingIndex(), subscriptions,
moduleNameBuildItem.getModuleName()));
}
return new SubscriptionMetaAnnotationsBuildItem(annotations);
}
Expand All @@ -52,7 +53,7 @@ public void registerSubscriptions(CombinedIndexBuildItem index,
AdditionalBeanBuildItem.Builder beans = AdditionalBeanBuildItem.builder().setUnremovable();
var moduleName = moduleNameBuildItem.getModuleName();
for (var subscription : index.getIndex().getAnnotations(FTLDotNames.SUBSCRIPTION)) {
var info = SubscriptionMetaAnnotationsBuildItem.fromJandex(subscription, moduleName);
var info = SubscriptionMetaAnnotationsBuildItem.fromJandex(index.getComputingIndex(), subscription, moduleName);
if (subscription.target().kind() != AnnotationTarget.Kind.METHOD) {
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import io.quarkus.gizmo.MethodDescriptor;
import xyz.block.ftl.Export;
import xyz.block.ftl.Topic;
import xyz.block.ftl.TopicDefinition;
import xyz.block.ftl.runtime.TopicHelper;
import xyz.block.ftl.v1.schema.Decl;

Expand All @@ -30,7 +29,7 @@ public class TopicsProcessor {

@BuildStep
TopicsBuildItem handleTopics(CombinedIndexBuildItem index, BuildProducer<GeneratedClassBuildItem> generatedTopicProducer) {
var topicDefinitions = index.getComputingIndex().getAnnotations(TopicDefinition.class);
var topicDefinitions = index.getComputingIndex().getAnnotations(FTLDotNames.TOPIC_DEFINITION);
log.infof("Processing %d topic definition annotations into decls", topicDefinitions.size());
Map<DotName, TopicsBuildItem.DiscoveredTopic> topics = new HashMap<>();
Set<String> names = new HashSet<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,18 @@

/**
*
* @return The name of the topic to subscribe to.
* @return The name of the topic to subscribe to. Cannot be used in conjunction with {@link #topicClass()}.
*/
String topic();
String topic() default "";

/**
*
* @return The subscription name
*/
String name();

/**
* The class of the topic to subscribe to, which can be used in place of directly specifying the topic name and module.
*/
Class<? extends Topic> topicClass() default Topic.class;
}