Files
halo-dark-mode-plugin/.agents/skills/halo-plugin-dev/references/server-reconciler.md
T
Serendipity edbf78f236 feat: 初始化 Halo 暗色模式插件
- Halo Plugin 后端(Java/Gradle),含 DarkModePlugin 主类和测试
- Vue 3 + TypeScript 前端 UI,包含主题切换组件和设置页面
- 暗色模式 CSS 变量和覆盖样式(布局/编辑器/表单/滚动条等)
- 设计文档和调查文档
- Halo 插件/主题开发 Agent Skills

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-06 21:03:00 +08:00

5.2 KiB

Reconciler (Controller Pattern)

A Kubernetes-style controller that watches Extension resources and continuously reconciles their desired state. More commonly used than Watcher in production plugins.

Source: Reconciler | ControllerBuilder | Controller

Basic Pattern

@Component
public class MyReconciler implements Reconciler<Reconciler.Request> {

    private final ExtensionClient client;

    public MyReconciler(ExtensionClient client) {
        this.client = client;
    }

    @Override
    public Result reconcile(Request request) {
        // Fetch the extension being reconciled
        var myExt = client.fetch(MyExtension.class, request.name());
        if (myExt.isEmpty()) {
            return Result.doNotRetry();
        }

        // Perform reconciliation logic
        var ext = myExt.get();
        // ... update annotations, labels, related resources, etc.

        // Update the extension if modified
        client.update(ext);

        return Result.doNotRetry();
    }

    @Override
    public Controller setupWith(ControllerBuilder builder) {
        return builder
            .extension(new MyExtension())
            .syncAllOnStart(false)
            .build();
    }
}

ControllerBuilder Options

Method Description
.extension(new MyExtension()) The extension type to watch (required)
.syncAllOnStart(true) Reconcile all existing instances on startup (default: true)
.syncAllListOptions(listOptions) Filter which existing instances to sync on start
.minDelay(Duration) Minimum retry delay (default: 5ms)
.maxDelay(Duration) Maximum retry delay (default: 1000s)
.workerCount(int) Number of concurrent workers (default: 1)
.onAddMatcher(matcher) Filter which add events to process
.onUpdateMatcher(matcher) Filter which update events to process
.onDeleteMatcher(matcher) Filter which delete events to process

Result Types

// Success, do not retry
return Result.doNotRetry();

// Requeue after a delay (for async operations or retry)
return Result.requeue(Duration.ofSeconds(30));

Lifecycle Integration

Reconcilers are auto-discovered by Spring. If you need to start/stop manually (e.g., conditional on plugin config):

@Component
public class MyReconciler implements Reconciler<Request>, SmartLifecycle {
    private Controller controller;
    private boolean running = false;

    @Override
    public Controller setupWith(ControllerBuilder builder) {
        this.controller = builder.extension(new MyExtension()).build();
        return controller;
    }

    @Override
    public void start() {
        if (controller != null && !running) {
            controller.start();
            running = true;
        }
    }

    @Override
    public void stop() {
        if (controller != null && running) {
            controller.dispose(); // or controller.stop()
            running = false;
        }
    }

    @Override
    public boolean isRunning() { return running; }
}

Finalizers

Use finalizers for cleanup before an extension is deleted:

@Override
public Result reconcile(Request request) {
    var ext = client.fetch(MyExtension.class, request.name()).orElse(null);
    if (ext == null) return Result.doNotRetry();

    // Check if being deleted
    if (ext.getMetadata().getDeletionTimestamp() != null) {
        // Perform cleanup
        doCleanup(ext);
        // Remove finalizer to allow deletion
        ExtensionUtil.removeFinalizers(ext.getMetadata(), Set.of("my-plugin/finalizer"));
        client.update(ext);
        return Result.doNotRetry();
    }

    // Add finalizer if not present
    ExtensionUtil.addFinalizers(ext.getMetadata(), Set.of("my-plugin/finalizer"));
    client.update(ext);

    // Normal reconciliation
    return Result.doNotRetry();
}

When to Use Reconciler vs Watcher

Reconciler Watcher
Use case Continuous state reconciliation, finalizers, retries One-time event handling
Persistence Queued, survives restarts In-memory only
Concurrency Configurable worker count Single threaded
Retry Built-in exponential backoff No retry