-
Notifications
You must be signed in to change notification settings - Fork 8
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: Initial Java runtime implementation #2318
feat: Initial Java runtime implementation #2318
Conversation
eebabf2
to
dff1bd2
Compare
@@ -0,0 +1,6 @@ | |||
# Ignore Gradle project-specific cache directory | |||
.gradle |
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.
Nooooooooooooooooooooooooooo
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.
This is the auto generated quarkus .gitignore, but it is coming at some point.
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.
LGTM!
I'd let @worstell have a pass over the Java code.
java-runtime/devel.go
Outdated
) | ||
|
||
// Files is the FTL Java runtime scaffolding files. | ||
func Files() *zip.Reader { return internal.ZipRelativeToCaller("scaffolding") } |
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.
I don't think this directory exists does it? Can we get rid of Files() altogether?
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.
I will remove it, I can re-add when it I add scaffolding.
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.
Just noting for future reference that this is a temporary situation until we figure out a better approach (eg. package servers).
Overall LGTM! I couldn't quite tell how topics (and other resources) in other modules are codegenned? Could be something for later, or I missed it. Once it's a bit more stable it might be an idea to add integration tests. |
I'd also be interested to see how we're going to write more complex inter-verb/module tests with the injection approach. I think we will at some point need an equivalent to the ModuleContext in Go. |
Topics are not quite there yet (well subscriptions really, the Topic). I have a plan for how to handle them, I should get to it soon.
Absolutely, I especially want to test interop with a go based module to make sure that everything is serializing / generating correctly. There is still heaps of work to do on tests. |
dff1bd2
to
f5e3538
Compare
fun echo(context: Context, req: EchoRequest): EchoResponse { | ||
val response = context.call(::time, Empty()) | ||
@Verb | ||
fun echo(req: EchoRequest, time: TimeClient): EchoResponse { |
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.
Looks like ctx is removed and replaced with DI?
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.
Yes, you directly reference the resources you need. This allows us to explicitly list this in the schema, as there is no way to get these resources without referencing them in the signature.
} | ||
var publish = cc.getMethodCreator("call", void.class); | ||
var helper = publish.invokeStaticMethod(MethodDescriptor.ofMethod(VerbClientHelper.class, "instance", VerbClientHelper.class)); | ||
publish.invokeVirtualMethod(MethodDescriptor.ofMethod(VerbClientHelper.class, "call", Object.class, String.class, String.class, Object.class, Class.class, boolean.class, boolean.class), helper, publish.load(name), publish.load(module), publish.loadNull(), publish.loadClass(Void.class), publish.load(false), publish.load(false)); |
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.
This is a long line! Would it be useful to split it up a bit?
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.
I need to setup auto formatting.
if (i.kind() == Type.Kind.PARAMETERIZED_TYPE) { | ||
var returnType = i.asParameterizedType().arguments().get(1); | ||
var paramType = i.asParameterizedType().arguments().get(0); | ||
try (ClassCreator cc = new ClassCreator(classOutput, iface.name().toString() + "_fit_verbclient", null, Object.class.getName(), iface.name().toString())) { | ||
if (launchModeBuildItem.isTest()) { | ||
cc.addAnnotation(TEST_ANNOTATION); | ||
cc.addAnnotation(Singleton.class); | ||
} | ||
var publish = cc.getMethodCreator("call", returnType.name().toString(), paramType.name().toString()); | ||
var helper = publish.invokeStaticMethod(MethodDescriptor.ofMethod(VerbClientHelper.class, "instance", VerbClientHelper.class)); | ||
var results = publish.invokeVirtualMethod(MethodDescriptor.ofMethod(VerbClientHelper.class, "call", Object.class, String.class, String.class, Object.class, Class.class, boolean.class, boolean.class), helper, publish.load(name), publish.load(module), publish.getMethodParam(0), publish.loadClass(returnType.name().toString()), publish.load(false), publish.load(false)); | ||
publish.returnValue(results); | ||
publish = cc.getMethodCreator("call", Object.class, Object.class); | ||
helper = publish.invokeStaticMethod(MethodDescriptor.ofMethod(VerbClientHelper.class, "instance", VerbClientHelper.class)); | ||
results = publish.invokeVirtualMethod(MethodDescriptor.ofMethod(VerbClientHelper.class, "call", Object.class, String.class, String.class, Object.class, Class.class, boolean.class, boolean.class), helper, publish.load(name), publish.load(module), publish.getMethodParam(0), publish.loadClass(returnType.name().toString()), publish.load(false), publish.load(false)); | ||
publish.returnValue(results); | ||
clients.put(iface.name(), new VerbClientBuildItem.DiscoveredClients(name, module,cc.getClassName())); | ||
} | ||
found = true; | ||
break; | ||
} else { | ||
throw new RuntimeException("@VerbClientDefinition can only be applied to interfaces that directly extend a verb client type with concrete type parameters and " + iface.name() + " does not have concrete type parameters"); | ||
} |
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.
Could all these if blocks be refactored out into a factory of sorts? There's a lot of very similar calls and variables. Maybe some DRY if it's appropriate?
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.
Even though it all looks similar each line is different enough that it can't really be turned generic. Basically this is due to the fact that there is 4 different client signatures, and each once needs a slightly different implementation.
public Map<String, String> start() { | ||
server = new FTLTestServer(); | ||
server.start(); | ||
return Map.of("ftl.endpoint", "http://localhost:" + server.getPort()); |
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.
This is stupid but maybe use 127.0.0.1 instead because of the mac firewall? #2298
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.
Ah if this is a client never mind.
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.
oops
4ead6b4
to
13c8ca7
Compare
|
||
output = outputTargetBuildItem.getOutputDirectory().resolve("main"); | ||
try (var out = Files.newOutputStream(output)) { | ||
// out.write(""" |
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.
nit: should this be deleted?
@@ -37,6 +40,8 @@ type ModuleConfig struct { | |||
Deploy []string `toml:"deploy"` | |||
// DeployDir is the directory to deploy from, relative to the module directory. | |||
DeployDir string `toml:"deploy-dir"` | |||
// GeneratedSchemaDir is the directory to generate protobuf schema files into. These can be picked up by language specific build tools | |||
GeneratedSchemaDir string `toml:"generated-schema-dir"` |
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.
what is the function of GeneratedSchemaDir
? is it needed rather than using the existing Schema
field in this config?
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.
Basically I dump the protos for the external modules I need to generate code for into this dir, and then run the code generation in Maven. It means you can check them into source control and have a build that can run without needing an FTL instance with those modules installed present.
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.
could we use the global build directory at <projectRoot>/.ftl
for this? in Go we put all the external module stubs in .ftl/go
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.
I could try that later, but this approach follows the standard maven code generation convention. It also allows for different modules in a multi module project to only generate clients for a subset of the modules on the server (this is not implemented yet). I would like to keep this approach for now to see how it works, and possibly re-visit it later.
|
||
@SuppressWarnings("unused") | ||
@Singleton | ||
public class FTLHttpHandler implements VerbInvoker { |
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.
it looks like http ingress verbs will be registered to use this handler on invocation, is that right?
why does java/kotlin need its own ingress logic? shouldn't we be able to invoke these verbs as normal on the runtime side, since the FTL controller will be responsible for serving the HTTP endpoint and routing the inbound ingress requests appropriately?
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.
This is purely about the Java API that is used (i.e. allowing you to use JAX-RS instead of a custom HTTP annotations). From the invocations side everything works as normal.
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.
ahh got it
d25dde4
to
4b84c0f
Compare
120f16d
to
ebdebe8
Compare
ebdebe8
to
5ce8f26
Compare
This is still very much a work in progress, however it contains a lot of basic functionality. So far this includes support for: - Verb invocations - HTTP ingress - Cron - Topics and Subscriptions - Basic testing of Verbs The existing Kotlin example has been migrated over to the new approach. At the moment the module is called Java even though it supports both, in future this will provide a base layer of functionality with some small language dependent features in separate Java/Kotlin modules.
This is still very much a work in progress, however it contains a lot of basic functionality.
So far this includes support for:
The existing Kotlin example has been migrated over to the new approach. At the moment the module is called Java even though it supports both, in future this will provide a base layer of functionality with some small language dependent features in separate Java/Kotlin modules.