Coder Social home page Coder Social logo

kora's Introduction

Kora framework

Kora is a jvm-based framework for building backend apps. It aims to be the solution for writing highly scalable and performant web applications using idiomatic java and kotlin with minimal amount of a boilerplate code.

Check out the full documentation here: Documentation

Name origin

Kora (Kore) aka Persephone - ancient greek goddess of Spring

Installation

    implementation platform("ru.tinkoff.kora:kora-parent:$koraVersion")
    annotationProcessor "ru.tinkoff.kora:annotation-processors:$koraVersion"

kora's People

Contributors

andrewiism avatar archiesw avatar devgkz avatar dmitrysadchikov avatar eld0727 avatar gnusinpavel avatar goodforgod avatar madfors avatar marsibarsi avatar mikesafonov avatar mitasov-ra avatar notsoold avatar nyquest avatar odanc avatar olex1313 avatar payurgin avatar slutmaker avatar squiry avatar tranquise avatar weapops avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kora's Issues

Unreadable non-existent type error

I was trying to update from 0.9.42 to 0.11.1 and got the following error:

[ksp] java.lang.IllegalArgumentException: Error type '<ERROR TYPE>' is not resolvable in the current round of processing.
	at com.squareup.kotlinpoet.ksp.KsTypesKt.toTypeName(ksTypes.kt:61)
	at com.squareup.kotlinpoet.ksp.KsTypesKt.toTypeName(ksTypes.kt:55)
	at com.squareup.kotlinpoet.ksp.KsTypesKt.toTypeName$default(ksTypes.kt:53)
	at ru.tinkoff.kora.kora.app.ksp.component.ComponentDependencyHelper.parseClaim(ComponentDependencyHelper.kt:63)
	at ru.tinkoff.kora.kora.app.ksp.component.ComponentDependencyHelper.parseDependencyClaim(ComponentDependencyHelper.kt:31)
	at ru.tinkoff.kora.kora.app.ksp.ProcessingState$ResolutionFrame$Component.<init>(ProcessingState.kt:18)
	at ru.tinkoff.kora.kora.app.ksp.GraphBuilder.processAllOf(GraphBuilder.kt:257)

The problem was with JdbcQueryExecutor usage, which is removed in newer version.
I found it by accident, but it would be nice to see more human-readable error about non-existent type usage.

Application shutdown with exit code 255 after few seconds of running.

Minimal application with "Hello world" from documentation, Groovy build config, Java code, JDK 17. Application shutdown with exit code 255 after few seconds of running.

$ ./gradlew run

> Task :run
SLF4J: No SLF4J providers were found.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.
дек. 14, 2022 2:29:32 PM org.xnio.Xnio <clinit>
INFO: XNIO version 3.8.4.Final
дек. 14, 2022 2:29:32 PM org.xnio.nio.NioXnio <clinit>
INFO: XNIO NIO Implementation Version 3.8.4.Final
дек. 14, 2022 2:29:32 PM org.jboss.threads.Version <clinit>
INFO: JBoss Threads version 3.1.0.Final
дек. 14, 2022 2:29:32 PM io.undertow.Undertow start
INFO: starting server: Undertow - 2.2.10.Final
дек. 14, 2022 2:29:32 PM io.undertow.Undertow start
INFO: starting server: Undertow - 2.2.10.Final
дек. 14, 2022 2:29:37 PM io.undertow.Undertow stop
INFO: stopping server: Undertow - 2.2.10.Final

> Task :run FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':run'.
> Process 'command '/usr/lib/jvm/bellsoft-java17-amd64/bin/java'' finished with non-zero exit value 255

Generation of protected methods in abstract repositories

Did some experiments with @Repository and figured out that you can define protected abstract method with @Query annotation:

@Query("""
    SELECT 1
    """)
protected abstract int foo();

Kora generates valid working implementation but adds public modifier, and class doesn't compile:

public protected int foo() { ... }

Unused ResponseCodeMapper import in openapi generated Kotlin Server

I've tried to use openapi generator with "kotlin_server" mode and found out that my server-only application does not compile because generated controller has ResponseCodeMapper import in it:

import ru.tinkoff.kora.http.client.common.annotation.ResponseCodeMapper;

This annotation isn't used anywhere in the file, just imported

Add mode for non-reactive Kotlin Server to OpenAPI Generator

At the moment Kora OpenAPI Generator supports the following modes:

  • java_client - non-reactive Java client
  • java_server - non-reactive Java server
  • reactive_client - reactive Java client (Project Reactor)
  • reactive_server - reactive Java server (Project Reactor)
  • kotlin_client - reactive Kotlin client (Project Reactor + Coroutines)
  • kotlin_server - reactive Kotlin server (Project Reactor + Coroutines)

As one can notice we lack non-reactive versions for kotlin_client and kotlin_server.

For small projects dealing with asynchronous programming is overkill.
Please consider adding these modes.

Add possibility to mark @Repository param as simple

I have table test with JSONB field data.

The following code works perfectly fine. Repository uses JdbcParameterColumnMapper<Test.Data> to map Test.Data to JSON string.

public record Test(Data data) {
  public record Data(String foo) {}
}

@Repository
public interface TestRepo extends JdbcRepository {
  @Query("""
    INSERT INTO test (data)
    VALUES (:test.data::jsonb)
    """)
  void insert(Test data);
}

@Component
public class TestDataJdbcParameterColumnMapper implements
    JdbcParameterColumnMapper<Test.Data> {

  private final JsonWriter<Test.Data> writer;

  public TestDataJdbcParameterColumnMapper(JsonWriter<Test.Data> writer) {
    this.writer = writer;
  }

  @Override
  public void set(PreparedStatement stmt, int index, @Nullable Test.Data value)
      throws SQLException {

    try {
      stmt.setString(index, new String(writer.toByteArray(value), StandardCharsets.UTF_8));
    } catch (IOException e) {
      throw new SQLException(e);
    }
  }
}

But when I add the following method:

@Query("""
UPDATE test
      SET data = :data::jsonb
""")
void replaceData(Test.Data data);

Kora treats Data object as EntityParameter and shows the error:

Parameter usage was not found in query: data

I tried to add @Mapping(TestDataJdbcParameterColumnMapper.class) to the data parameter, but it didn't help.

Seems like there's no way to tell Kora that Data should be processed as SimpleParameter. It would be nice to add such annotation.

Unable to obtain mapper for Kotlin database entity classes

When trying to compile kotlin code, ksp throws something like this

Required dependency was not found and candidate class ru.tinkoff.kora.database.jdbc.mapper.result.JdbcResultSetMapper<YourEntity> is not final
e: Error occurred in KSP, check log for detail

Even if primary constructor or @EntityConstructor with secondary constructor is used

Kora version - 0.11.0
Kotlin version - 1.8.0
KSP version - 1.8.0-1.0.9

Escape `?` tokens in SQL in generated JdbcReposiory impl

In PostgreSQL we have JSON/JSONB operators: ?, ?|, ?& etc. (https://www.postgresql.org/docs/12/functions-json.html)

PostgreSQL JDBC driver has an escape sequence for ? - ??.

Is it possible to automatically escape ? in @Query annotations, as query syntax in them allows only named params, so it isn't clear that ? still needs to be escaped.

Example

SELECT *
FROM foo
WHERE foo.name = :name AND foo.json_data ? :keyName

Produces

ru.tinkoff.kora.database.jdbc.RuntimeSqlException: No value specified for parameter 3.

@KafkaPublisher support for custom methods

Feature will allow users to create custom methods that are analogs to @KafkaListener ones but for producer

This will allow for easier use of @KafkaPubisher with ability to specify configuration path for topic in annotation on method to fix this producers method for declared topic only

@KafkaPublisher("test")
public interface TestProducer extends Producer<String,String> {

   @KafkaPublisher.Topic("path.for.topic.in.config")
   void send(String key, String value, Headers headers);
}

NPE in KSP when trying to add @Json to data-class property

@Component
@Tag(Json::class)
class JsonJdbcParameterColumnMapper<T>(private val writer: JsonWriter<T>) : JdbcParameterColumnMapper<T> {

  @Throws(SQLException::class)
  override fun set(stmt: PreparedStatement, index: Int, value: T?) {
    try {
      stmt.setString(index, String(writer.toByteArray(value), StandardCharsets.UTF_8))
    } catch (e: IOException) {
      throw SQLException(e)
    }
  }

}

@Repository
interface FooRepository {

    @Query("""
      INSERT INTO foo (bar)
      VALUES
      (:foo.bar::jsonb)
    """)
    fun insert(foo: Foo)
    
    @Query("""
      UPDATE foo
      SET bar = :bar::jsonb
    """)
    fun updateBar(@Json bar: Foo.Bar)
}

data class Foo(
  // @Json
  // should be added to make everything work
  val bar: Bar
) {
    data class Bar(val s: String)
}

If I uncomment // @Json :kspKotlin task fails with message:

> Task :kspKotlin FAILED
2 actionable tasks: 2 executed
e: [ksp] java.lang.NullPointerException
	at ru.tinkoff.kora.ksp.common.FieldFactory.get(FieldFactory.kt:26)
	at ru.tinkoff.kora.database.symbol.processor.jdbc.StatementSetterGenerator.generate(StatementSetterGenerator.kt:96)
	at ru.tinkoff.kora.database.symbol.processor.jdbc.JdbcRepositoryGenerator.generate(JdbcRepositoryGenerator.kt:89)
	at ru.tinkoff.kora.database.symbol.processor.jdbc.JdbcRepositoryGenerator.generate(JdbcRepositoryGenerator.kt:55)
	at ru.tinkoff.kora.database.symbol.processor.RepositoryBuilder.build(RepositoryBuilder.kt:50)
	at ru.tinkoff.kora.database.symbol.processor.RepositorySymbolProcessor.processClass(RepositorySymbolProcessor.kt:45)
	at ru.tinkoff.kora.database.symbol.processor.RepositorySymbolProcessor.access$processClass(RepositorySymbolProcessor.kt:15)
	at ru.tinkoff.kora.database.symbol.processor.RepositorySymbolProcessor$processRound$1.invoke(RepositorySymbolProcessor.kt:26)
	at ru.tinkoff.kora.database.symbol.processor.RepositorySymbolProcessor$processRound$1.invoke(RepositorySymbolProcessor.kt:26)
	at ru.tinkoff.kora.ksp.common.KspCommonUtilsKt$visitClass$1.visitClassDeclaration(KspCommonUtils.kt:208)
	at com.google.devtools.ksp.symbol.impl.kotlin.KSClassDeclarationImpl.accept(KSClassDeclarationImpl.kt:136)
	at ru.tinkoff.kora.ksp.common.KspCommonUtilsKt.visitClass(KspCommonUtils.kt:204)
	at ru.tinkoff.kora.database.symbol.processor.RepositorySymbolProcessor.processRound(RepositorySymbolProcessor.kt:26)
	at ru.tinkoff.kora.ksp.common.BaseSymbolProcessor.process(BaseSymbolProcessor.kt:87)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$6$1.invoke(KotlinSymbolProcessingExtension.kt:291)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$6$1.invoke(KotlinSymbolProcessingExtension.kt:289)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.handleException(KotlinSymbolProcessingExtension.kt:394)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:289)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:123)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:99)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:257)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:42)
	at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:115)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:248)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:88)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:47)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:168)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:53)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:100)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:46)
	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
	at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1486)
	at jdk.internal.reflect.GeneratedMethodAccessor52.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:712)
	at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)

e: Error occurred in KSP, check log for detail

FAILURE: Build failed with an exception.

Add more examples to documentation

Hello there,
I'm a newbie in Kora. Past two weeks I've faced with many issue during development of my REST-api app.
It would be great and helpful to extend current Kora documentation with more examples.

  1. Example of POST method with body here
    Like that
    @Json
    @HttpRoute(method = HttpMethod.POST, path = "/books")
    public BookEntity createBook(@Json BookCreationRequest book) {
        LOGGER.info("createBook is called with {}", book);
        return bookRepository.addNewBook(book);
    }
  1. Example of how to customise returned http code of GET/POST endpoint (404, 201 etc.) here
  2. Example of repository that creates new row in database and returns id here
    I mean something like that
    @Query("insert into books(title, author, published_date) values(:title, :author, :publishedDate) returning id")
    int addNewBook(String title, String author, LocalDate publishedDate);

@Repository generated components are not recognised by graph if their indirect parents are requested

Assume I have the following class hierarchy:

interface FooRepository { ... }

@Repository
abstract class AbstractFooRepository( ... ) : FooRepository, JdbcRepository { ... }

@Generated
class `$AbstractFooRepository_Impl`( ... ) : AbstractFooRepository( ... ) {
  ...
}

If I then create the new component:

@Component
class FooService(val repo: FooRepository)

I get the following error:

Required dependency type FooRepository was not found and can't be autocreated.

But if I change repo type from FooRepository to AbstractFooRepository everything compiles just fine

OpenAPI generator - 400 Bad Request with body "data" if body is null

I generated POST method from openapi and if I try to send a request with body "null" I get the following response:

HTTP/1.1 400 Bad Request
Connection: keep-alive
Server: kora/undertow
Content-Length: 4
Content-Type: text/plain; charset=utf-8
Date: Wed, 21 Jun 2023 07:21:42 GMT

data

I've already investigated and figured out that such strange answer is caused by NPE in ru.tinkoff.kora.json.module.http.server.JsonReaderHttpServerRequestMapper<T>:22:

    @Override
    public Mono<T> apply(HttpServerRequest request) {
        return ReactorUtils.toByteArrayMono(request.body())
            .handle((bytes, sink) -> {
                try {
                    sink.next(this.reader.read(bytes)); // <-- NPE HERE
                } catch (Exception e) {
                    var httpException = HttpServerResponseException.of(e, 400, e.getMessage());
                    sink.error(httpException);
                }
            });
    }

Text "data" came from HandleFuseableConditionalSubscriber#next:

data = Objects.requireNonNull(o, "data");

I'd be mush more better to allow null body and maybe even to generate validation messages similar to messages from JsonReader

Kora doesn't add abstract @Repository generated implementations to DI container

This works:

@Repository
public interface FooRepository extends JdbcRepository {

  @Query("""
      SELECT
        name
      FROM foo
      WHERE id = :id
      """)
 Foo findById(Integer id);

}

But this:

@Repository
public abstract class FooRepository implements JdbcRepository {

  @Query("""
      SELECT
        name
      FROM foo
      WHERE id = :id
      """)
 public abstract Foo findById(Integer id);

}

causes the error:

Required dependency was not found and candidate class com.example.FooRepository is not final

[KSP] JdbcResultSetMapper<ru.tinkoff.kora.database.common.UpdateCount> is not final or not found

Looks like ksp doesn't generate JdbcResultSetMapper

Repository:

@Query(
        """
        update my_entity
        set
            name = :entity.name
        where id = :entity.id
        """
    )
    fun update(entity: Entity): UpdateCount

Error:

[ksp]  Required dependency was not found and candidate class ru.tinkoff.kora.database.jdbc.mapper.result.JdbcResultSetMapper<ru.tinkoff.kora.database.common.UpdateCount> is not final

Kora version: 0.11.2

Server respond with 405 for wildcard HttpRoute when there is a more specific overlapping HttpRoute with different method

Hi here!

Here is a simple controller

@HttpController
public final class TestController {

    @HttpRoute(method = HttpMethod.GET, path = "/test")
    public void testGet() {
    }

    @HttpRoute(method = HttpMethod.POST, path = "/*")
    public void testPost() {
    }
}

I expect server to respond with 200 to POST /test, but in fact it gives me 405.
In early versions (<0.9.41) it gave me 200 as I expected.

OpenAPI generator generates kt data classes with errors when using discriminator

In my openapi.yaml I have:

components:
  schemas:
    ValidationErrorDto:
      title: ValidationErrorDto
      type: object
      oneOf:
        - $ref: '#/components/schemas/BlockValidationErrorDto'
        - $ref: '#/components/schemas/PropValidationErrorDto'
      discriminator:
        propertyName: type
        mapping:
          'Block.Error1': '#/components/schemas/BlockValidationErrorDto'
          'Block.Error2': '#/components/schemas/BlockValidationErrorDto'
          'Prop.Error1': '#/components/schemas/PropValidationErrorDto'
          'Prop.Error2': '#/components/schemas/PropValidationErrorDto'

    PropValidationErrorDto:
      title: PropValidationErrorDto
      type: object
      properties:
        type:
          type: string

    BlockValidationErrorDto:
      title: PropValidationErrorDto
      type: object
      properties:
        type:
          type: string
        key:
          type: integer

Generated data classes does not compile, here's all the problems I found:

  • PropValidationErrorDto
    • duplicate empty constructor
    • missing Type in extends/implements section
  • BlockValidationErrorDto
    • missing Type in extends/implements section

Also I noticed that only one @JsonDiscriminatorValue specified, but I think It's the other issue. (#174)

Generated code

  /**
   * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech) (6.2.1).
   * https://openapi-generator.tech
   * Do not edit the class manually.
   */
  package ru.tinkoff.datadetective.editor.backend.api.dto
  
  import com.fasterxml.jackson.core.JsonGenerator
  import ru.tinkoff.kora.common.Component
  import ru.tinkoff.kora.json.common.annotation.*
  import ru.tinkoff.kora.json.common.JsonWriter
  import ru.tinkoff.kora.json.common.EnumJsonWriter
  
  
  
  /**
   * PropValidationErrorDto
   */
  @ru.tinkoff.kora.json.common.annotation.JsonWriter
  @ru.tinkoff.kora.common.annotation.Generated("openapi generator kora client")
  @ru.tinkoff.kora.json.common.annotation.JsonDiscriminatorValue("Prop.Error2")
  data class PropValidationErrorDto @ru.tinkoff.kora.json.common.annotation.JsonReader constructor (
  ) :  {
  
    constructor(
    ) : this()
  
  
  }
/**
 * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech) (6.2.1).
 * https://openapi-generator.tech
 * Do not edit the class manually.
 */
package ru.tinkoff.datadetective.editor.backend.api.dto

import com.fasterxml.jackson.core.JsonGenerator
import ru.tinkoff.kora.common.Component
import ru.tinkoff.kora.json.common.annotation.*
import ru.tinkoff.kora.json.common.JsonWriter
import ru.tinkoff.kora.json.common.EnumJsonWriter



/**
 * BlockValidationErrorDto
 */
@ru.tinkoff.kora.json.common.annotation.JsonWriter
@ru.tinkoff.kora.common.annotation.Generated("openapi generator kora client")
@ru.tinkoff.kora.json.common.annotation.JsonDiscriminatorValue("Block.Error2")
data class BlockValidationErrorDto @ru.tinkoff.kora.json.common.annotation.JsonReader constructor (
  @JsonField("key")
    val key: Int?

) :  {

  constructor(
  ) : this(null)


}

@KafkaListener headers support

Features will allow to receive Kafka Headers for custom methods in @KafkaListener

public class KafkaListenerClass {
    @KafkaListener("test.config.path")
    public void process(String key, String value, Headers headers) {
    }
}

@KafkaListener & @KafkaPublisher support for @Json

Features will allow to mark Listener/Publisher custom value / Record Generic with @Json and this will result in automatic generation of Deserialized/Serialized for such with in/out of Json without the need for developer to write custom Json mapper for such values

Custom value:

public class KafkaListenerClass {


    @KafkaListener("test.config.path")
    public void process(@Json JsonEvent value) {
    }
}

Record generic:

public class KafkaListenerClass {
    @KafkaListener("test.config.path")
    public void process(ConsumerRecord<String, @Json JsonEvent> event) {
    }
}

Make @JsonDiscriminatorValue repeatable

At the moment I can put only one @JsonDiscriminatorValue on the class.

But OpenAPI allows to map multiple disriminators to one type via mapping setting like so:

ValidationErrorDto:
  title: ValidationErrorDto
  type: object
  oneOf:
    - $ref: '#/components/schemas/BlockValidationErrorDto'
    - $ref: '#/components/schemas/PropValidationErrorDto'
  discriminator:
    propertyName: type
    mapping:
      'Block.Error1': '#/components/schemas/BlockValidationErrorDto'
      'Block.Error2': '#/components/schemas/BlockValidationErrorDto'
      'Prop.Error1': '#/components/schemas/PropValidationErrorDto'
      'Prop.Error2': '#/components/schemas/PropValidationErrorDto'

With the above spec only last discriminators from mapping will be added on generated data classes.

@JsonSkip is not appliable to Kotlin interface property

Assume I have the following type hierarchy:

interface Foo {
  val empty
  
  val notEmpty get() = !empty
}

data class Bar(override val empty) : Foo

If I try to add @ru.tinkoff.kora.json.common.annotation.JsonSkip annotation to notEmpty property, I get the following error:

This annotation is not applicable to target 'member property without backing field or delegate'

However, if I then serialize Bar(false) instance with generated JsonWriter<Bar>, I get the following JSON:

{
  "empty": false,
  "notEmpty": true
}

So, there's currently no way to exclude inherited default property from serialization. The only workaround is to explicitly declare notEmpty inside Bar with backing field like so:

data class Bar(override val empty) : Foo {
  @JsonSkip
  override val optional = !required // notice I'm not using get() anymore, so !required is stored in field
}

But this kills the whole point of creating notEmpty property in interface.

Support @Embedded dto-s for repositories

Example:

SELECT id, text, owner.id AS owner_id, owner.nickname AS owner_nickname
FROM post
JOIN profile owner ON onwer.id = post.owner_id
WHERE id = ...
record Post(UUID id, String text, @Embedded Profile owner) {}

record Profile(UUId id, String nickname) {}

By default prefix is {fieldName}_, custom prefix with @Embedded("custom_")

Accidental StacklessClosedChannelException when using AsyncHttpClient

When I use the following Koltin extension for AsyncHttpClient:

client.execute(request) { resp ->
  // ...
}

sometimes requests are failed with this error:

ru.tinkoff.kora.http.client.common.HttpClientConnectionException: io.netty.channel.StacklessClosedChannelException
    at ru.tinkoff.kora.http.client.async.AsyncHttpClient.lambda$execute$1(AsyncHttpClient.java:39)
    at reactor.core.publisher.Mono.lambda$onErrorMap$28(Mono.java:3763)
    at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:94)
    at ru.tinkoff.kora.common.util.ReactorContextHook$ContextPropagator.onError(ReactorContextHook.java:80)
    at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onError(FluxContextWrite.java:121)
    at ru.tinkoff.kora.common.util.ReactorContextHook$ContextPropagator.onError(ReactorContextHook.java:75)
    at reactor.core.publisher.MonoCreate$DefaultMonoSink.error(MonoCreate.java:201)
    at ru.tinkoff.kora.http.client.async.MonoSinkStreamAsyncHandler.onThrowable(MonoSinkStreamAsyncHandler.java:66)
    at org.asynchttpclient.netty.NettyResponseFuture.abort(NettyResponseFuture.java:277)
    at org.asynchttpclient.netty.request.WriteListener.abortOnThrowable(WriteListener.java:50)
    at org.asynchttpclient.netty.request.WriteListener.operationComplete(WriteListener.java:61)
    at org.asynchttpclient.netty.request.WriteCompleteListener.operationComplete(WriteCompleteListener.java:28)
    at org.asynchttpclient.netty.request.WriteCompleteListener.operationComplete(WriteCompleteListener.java:20)
    at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:578)
    at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:552)
    at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:491)
    at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:616)
    at io.netty.util.concurrent.DefaultPromise.setFailure0(DefaultPromise.java:609)
    at io.netty.util.concurrent.DefaultPromise.tryFailure(DefaultPromise.java:117)
    at io.netty.channel.AbstractChannel$AbstractUnsafe.safeSetFailure(AbstractChannel.java:999)
    at io.netty.channel.AbstractChannel$AbstractUnsafe.write(AbstractChannel.java:860)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.write(DefaultChannelPipeline.java:1367)
    at io.netty.channel.AbstractChannelHandlerContext.invokeWrite0(AbstractChannelHandlerContext.java:877)
    at io.netty.channel.AbstractChannelHandlerContext.invokeWriteAndFlush(AbstractChannelHandlerContext.java:940)
    at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:966)
    at io.netty.channel.AbstractChannelHandlerContext.writeAndFlush(AbstractChannelHandlerContext.java:934)
    at io.netty.channel.AbstractChannelHandlerContext.invokeWriteAndFlush(AbstractChannelHandlerContext.java:943)
    at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:966)
    at io.netty.channel.AbstractChannelHandlerContext.writeAndFlush(AbstractChannelHandlerContext.java:934)
    at io.netty.channel.AbstractChannelHandlerContext.invokeWriteAndFlush(AbstractChannelHandlerContext.java:943)
    at io.netty.channel.AbstractChannelHandlerContext$WriteTask.run(AbstractChannelHandlerContext.java:1247)
    at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:174)
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:167)
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:470)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:566)
    at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.base/java.lang.Thread.run(Unknown Source)
Caused by: io.netty.channel.StacklessClosedChannelException: null
    at io.netty.channel.AbstractChannel$AbstractUnsafe.write(Object, ChannelPromise)(Unknown Source)

I don't know how to reproduce this, it just happens time to time.

Versions

JVM: corretto-17.0.6
Kora: 0.11.10
Kotlin: 1.8.22
kotlinx-coroutines: 1.7.1
KSP: 1.8.22-1.0.11

Broken Abstract @Repository generation in KSP

I've tried to generate implementation for Abstract @repository class, but generated implementation doesn't compile:

public class `$AbstractFooRepository_Impl`(
  _jdbcConnectionFactory: JdbcConnectionFactory,
  _executor: Executor,
  _result_mapper_1: JdbcResultSetMapper<Foo>
) : AbstractFooRepository(/* MISSING CONSTRUCTOR PARAMETER */), JdbcRepository {
  // ...
}

P.S. It turned out that the reason was that the constructor of my abstract class was protected. I removed the protected modifier and now everything works:

// THIS DOESN'T WORK
abstract class AbstractFooRepository protected constructor(/* params */)

// THIS DOES
abstract class AbstractFooRepository(/* params */)

Exception when interface with @Repository extends any other interface

For example: we have some repository interface like

@Repository
interface DocumentRepository extends JdbcQueryExecutorAccessor, BatchOperation{
    @Query("insert into ....")
    void someInsert();
   @Query("insert into .... from :fields ...")
    void someBatchInsert(@Batch List<String> fields);
}

where BatchOperation just common interface for set of different repositories, it looks like:

interface BatchOperation {
   void someBatchInsert(List<String> fields);
}

it contains neither @Repositor nor @Query annotations, as we don't want to generate such repository.
We are getting compilation exception which tells us, that BatchOperations abstract methods don't have annotations @Query.
Exception: error: Non-static abstract method has no @Query annotation

NoClassDefFoundError: KafkaConsumerMetrics after upgrading to 0.12.0

I bumped Kora version to 0.12.0 and previously working app now fails on startup with the following error:

Exception in thread "main" java.lang.NoClassDefFoundError: ru/tinkoff/kora/kafka/common/consumer/telemetry/KafkaConsumerMetrics
    at java.base/java.lang.ClassLoader.defineClass1(Native Method)
    at java.base/java.lang.ClassLoader.defineClass(Unknown Source)
    at java.base/java.security.SecureClassLoader.defineClass(Unknown Source)
    at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(Unknown Source)
    at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(Unknown Source)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(Unknown Source)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(Unknown Source)
    at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Unknown Source)
    at java.base/sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(Unknown Source)
    at java.base/sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Unknown Source)
    at java.base/sun.reflect.generics.tree.ClassTypeSignature.accept(Unknown Source)
    at java.base/sun.reflect.generics.visitor.Reifier.reifyTypeArguments(Unknown Source)
    at java.base/sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Unknown Source)
    at java.base/sun.reflect.generics.tree.ClassTypeSignature.accept(Unknown Source)
    at java.base/sun.reflect.generics.repository.FieldRepository.computeGenericType(Unknown Source)
    at java.base/sun.reflect.generics.repository.FieldRepository.getGenericType(Unknown Source)
    at java.base/java.lang.reflect.Field.getGenericType(Unknown Source)
    ... and then trace of my app, in generated ApplicationGraph

I do not have any Kafka related dependencies and settings in my project.

Kora version: 0.12.0
Kotlin version: 1.8.22
KSP version: 1.8.22-1.0.11

Koltin repositories is still broken for single result

Given repository

interface EntityRepository{
 fun findEntity(id): Entity?
 }

Generates repository with implementation like this

          _stmt.executeQuery().use { _rs ->
           val _result = _findByIdWithHidden_resultMapper.apply(_rs)
           _telemetry.close(null)
           return _result
         }

And mapper like this

@Generated("ru.tinkoff.kora.database.symbol.processor.jdbc.extension.JdbcTypesExtension")
public class `$Entity_JdbcResultSetMapper`() : JdbcResultSetMapper<Entity> {
 public override fun apply(_rs: ResultSet): Partner {
   val _idx_id = _rs.findColumn("id");
   val id = _rs.getLong(_idx_id)
   val _row = Entity(id)
   return _row
 }
}

Perhaps it should implement JdbcResultSetMapper<Entity?> and call next(), because right now it throws SQL exception with cause - ResultSet not positioned properly, you need to call next

Kora documentation new engine

Move Kora documentation to new engine like docusaurus with more feature set

Need to investigate first for best engine possible for desired feature set:

  • Separate documentation for different Kora Versions (check Micronaut doc for examples)
  • Switch for Java/Kotlin examples in one place, same for Maven/Gradle if needed (check Micronaut doc for examples)
  • Different language support (Russian / English)

Kora Examples

Publish separate repository with Kora Examples for all core components that can be used by newbie as simple guide for developing, trying, testing Kora features and a more interactive way of communication/investing Kora features rather than documentation

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.