agentsclimarketplace

Kora di runtime

Skill kora-projects/kora-skills/plugins/kora-v1/skills/kora-di-runtime

Agent Skills for Kora Framework — compile-time DI for Java/Kotlin backend development.

Install
npx -y skills add kora-projects/kora-skills --skill kora-di-runtime

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Covers Kora runtime dependency-injection behavior - the Graph lifecycle, component init/release, disambiguation, collection injection, lazy/optional wrappers, and interception. Use when a component must start without being a dependency (@Root from ru.tinkoff.kora.common.annotation), when implementing init()/release() via Lifecycle or LifecycleWrapper, when disambiguating multiple beans of one interface with @Tag (tag marker class, @Tag(Tag.Any.class)), when collecting every implementation with All<T>, when breaking refresh chains or cycles with ValueOf<T>, when an optional dependency needs @Nullable, or when wrapping a component during graph build with GraphInterceptor. Does not cover compile-time @KoraApp/@Module/@Component wiring (see kora-di-compile).

SKILL.md

10.4 KB, as published. Nobody here has run it

Kora DI Runtime — Runtime Injection Behavior

Kora Version: 1.x
Focus: Runtime DI — lifecycle, tags, collections, lazy dependencies, interception.

Read this first when:

  • Marking components as @Root for automatic startup
  • Implementing Lifecycle for init/release logic
  • Disambiguating with @Tag, collecting with All<T>
  • Using ValueOf<T> for lazy dependencies
  • Wrapping components with GraphInterceptor

References: See references/ for detailed guides on each topic.


Quick Start

@Root — Self-Starting Components

Only components that are dependencies of other components, or marked @Root (ru.tinkoff.kora.common.annotation.Root), are instantiated at runtime. @Root goes on a @Component class or on a @KoraApp/@Module factory method.

When to use @Root: HTTP/gRPC servers, Kafka consumers, cache warmers, schedulers, startup tasks — anything that must run even though nothing depends on it.

A self-starting worker:

import ru.tinkoff.kora.application.graph.Lifecycle;
import ru.tinkoff.kora.common.Component;
import ru.tinkoff.kora.common.annotation.Root;

@Root
@Component
public final class ServerRunner implements Lifecycle {
    private final ServerConfig config; // a @ConfigSource interface, injected as a component

    public ServerRunner(ServerConfig config) {
        this.config = config;
    }

    @Override
    public void init() throws Exception {
        System.out.println("Starting server on port " + config.port());
        // Bind socket, start accepting traffic
    }

    @Override
    public void release() throws Exception {
        System.out.println("Stopping server");
        // Stop server, release connections
    }
}

Config is supplied as a separate @ConfigSource component (see kora-config-hocon), never as a constructor-parameter annotation:

@ConfigSource("server")
public interface ServerConfig {
    int port();
}

Warning: Forget @Root and a non-dependency worker won't start — the application exits silently after the Graph is built.

Learn more: references/root-component-reference.md — Complete @Root guide with patterns and troubleshooting.


Component Lifecycle

Components with initialization/cleanup logic implement Lifecycle (ru.tinkoff.kora.application.graph.Lifecycle):

@Root
@Component
public final class DatabasePool implements Lifecycle {
    private final DataSource dataSource;
    private final ScheduledExecutorService scheduler;
    
    public DatabasePool(DataSource dataSource) {
        this.dataSource = dataSource;
        this.scheduler = Executors.newSingleThreadScheduledExecutor();
    }
    
    @Override
    public void init() throws Exception {
        try (Connection conn = dataSource.getConnection()) {
            System.out.println("Database connection OK");
        }
        scheduler.scheduleAtFixedRate(this::cleanup, 5, 5, TimeUnit.MINUTES);
    }
    
    @Override
    public void release() throws Exception {
        scheduler.shutdown();
        if (!scheduler.awaitTermination(30, TimeUnit.SECONDS)) {
            scheduler.shutdownNow();
        }
        if (dataSource instanceof AutoCloseable) {
            ((AutoCloseable) dataSource).close();
        }
    }
}

Key points:

  • init() is called after component creation, before use; release() during graceful shutdown (SIGTERM)
  • Release order is the reverse of init order
  • Components are initialized as parallel as the dependency Graph allows
  • For factory methods, wrap with LifecycleWrapper — see Advanced DI Patterns for exact signature

Generic Factories

Generic methods in @Module interfaces become "generic factories" usable for ANY matching type. Use deliberately, or move helpers to top-level private static methods. See Advanced DI Patterns.

Learn more: references/lifecycle-reference.md — LifecycleWrapper, graceful shutdown, post-commit actions.


Tags and Collections

@Tag for Disambiguation

When multiple implementations of one interface exist, use @Tag (ru.tinkoff.kora.common.Tag) to select which one to inject. The tag is a marker class, not a string literal, so navigation and refactoring stay safe:

public final class RedisTag {}
public final class CaffeineTag {}

@Tag(RedisTag.class)
@Component
public final class RedisCache implements Cache {}

@Tag(CaffeineTag.class)
@Component
public final class CaffeineCache implements Cache {}

@Component
public final class UserService {
    private final Cache redisCache;
    private final Cache localCache;
    
    public UserService(
        @Tag(RedisTag.class) Cache redisCache,
        @Tag(CaffeineTag.class) Cache localCache
    ) {
        this.redisCache = redisCache;
        this.localCache = localCache;
    }
}

All<T> for Collections

Inject every untagged implementation of a type. All<T> (ru.tinkoff.kora.application.graph.All) extends List<T>:

import ru.tinkoff.kora.application.graph.All;

@Component
public final class NotificationService {
    private final List<Notifier> notifiers;

    public NotificationService(All<Notifier> notifiers) {
        this.notifiers = List.copyOf(notifiers);
    }

    public void notify(String message) {
        notifiers.forEach(n -> n.send(message));
    }
}

To collect every implementation including tagged ones, request @Tag(Tag.Any.class):

public NotificationService(@Tag(Tag.Any.class) All<Notifier> notifiers) { ... }

@Tag(SomeTag.class) All<T> collects only the components registered under SomeTag.

Learn more:


ValueOf<T> — Lazy Dependencies

ValueOf<T> (ru.tinkoff.kora.application.graph.ValueOf) provides lazy access via get() and decouples lifecycles: depending on ValueOf<B> tells the Graph that this component is NOT refreshed when B changes. ValueOf also exposes refresh().

import ru.tinkoff.kora.application.graph.ValueOf;

@Component
public final class ApiClient {
    private final ValueOf<AuthConfig> config;

    public ApiClient(ValueOf<AuthConfig> config) {
        this.config = config;
    }

    public Response get(String url) {
        var currentConfig = config.get(); // always the latest instance
        // ApiClient is NOT recreated when AuthConfig refreshes
        return doGet(url, currentConfig);
    }
}

Use cases: breaking refresh cascades, breaking circular dependencies, lazy access to a component that may not be ready at construction.

Optional dependencies use a @Nullable constructor parameter (any @Nullable: jakarta.annotation.Nullable, javax.annotation.Nullable, or org.jetbrains.annotations.Nullable); the Graph then tolerates the absence of that component instead of failing the build.

Learn more: references/optional-dependency-reference.md — @Nullable and ValueOf patterns.


GraphInterceptor — Component Wrapping

GraphInterceptor<T> (ru.tinkoff.kora.application.graph.GraphInterceptor) wraps or modifies a component during graph construction. Its contract mirrors Lifecycle except init/release return the (possibly replaced) instance that other components will then receive:

@Component
public final class MetricsInterceptor implements GraphInterceptor<HttpClient> {
    private final MeterRegistry meterRegistry;
    
    public MetricsInterceptor(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }
    
    @Override
    public HttpClient init(HttpClient client) {
        return new MetricsHttpClient(client, meterRegistry);
    }
    
    @Override
    public HttpClient release(HttpClient client) {
        return client;
    }
}

Use cases: Metrics, tracing, circuit breakers, logging proxies.

Learn more: references/graph-interceptor-reference.md — Complete interceptor patterns.


Common Pitfalls

ProblemSolution
HTTP/GRPC server doesn't startMark server component with @Root
Kafka consumer not pollingMark consumer with @Root
Component silently not createdCheck it's used by @Root or marked @Root itself
Component restarted on config changeUse ValueOf<T> for lazy dependency
Ambiguous dependency errorUse @Tag to disambiguate
LifecycleWrapper factory returns wrong typeReturn Wrapped<T>, use constructor (see Advanced DI)
Generic method in @Module causes implicit bindingsMove helpers to top-level private static methods (see Advanced DI)

Checklist

- [ ] Component marked @Root if it must start automatically?
- [ ] Implements Lifecycle for init/release logic?
- [ ] Resources properly released in release()?
- [ ] Using @Tag to disambiguate multiple implementations?
- [ ] Using All<T> for untagged collections, @Tag(Tag.Any.class) List<T> for all components?
- [ ] Using ValueOf<T> for lazy dependencies?
- [ ] Using GraphInterceptor for component wrapping?

Assets

Templates in assets/: Application, RootComponent, LifecycleComponent, LifecycleFactory, TaggedComponent, CollectionComponent, GraphInterceptor, ValueOfComponent, build configs.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.