Files
netneutrality/docs/architecture.md
T
2026-10-19 08:21:42 -04:00

11 KiB

Architecture Overview

Net Neutrality is a Java 25 Discord bot built on top of JDA. It is packaged as a single fat JAR (via the Maven Shade plugin) whose entry point is me.bobert.netneutrality.App.

Big picture

Discord sends events over the gateway. A single JDA EventListener (me.bobert.netneutrality.hooks.EventsHandler) receives every event and turns it into a Context — a high-level abstraction over that event. Each context is then routed to one of three execution paths:

                              ┌──────────────────────────────┐
     Discord Gateway  ───────▶│        EventsHandler         │
        (JDA events)          │  event class → Context factory│
                              └───────────────┬──────────────┘
                                              │ builds a Context
                    ┌─────────────────────────┼───────────────────────────┐
                    ▼                          ▼                           ▼
            CommandContext              TaskContext                (no Context)
                    │                          │                    lambda /
                    ▼                          ▼                    consumer runs
            CommandProcessor            TaskManager.handle()        directly
            .dispatch()                 (lookup task by id)          (e.g. GuildJoin)
                    │                          │
                    ▼                          ▼
             Command.execute()          Task.execute()
  1. Command flow — the context is a CommandContext; it is handed to CommandProcessor which resolves and runs a Command. See commands.md.
  2. Task flow — the context is a TaskContext; it is handed to TaskManager which looks up a Task by its id and runs it. See tasks.md.
  3. Lambda flow — for simple events (e.g. GuildJoinEvent, GuildLeaveEvent) a consumer runs directly and no context is routed onward. See events.md.

Around this core are the supporting subsystems: persistence (Hibernate + Flyway), music (a remote Lavalink node), the web API, and the threading/scheduling infrastructure.

Technology stack

Concern Library Version
Discord API net.dv8tion:JDA 6.4.1
Audio engine Lavalink v4 node (ghcr.io/lavalink-devs/lavalink) 4.2
Audio client dev.arbjerg:lavalink-client 3.4.0
ORM org.hibernate.orm:hibernate-core 7.1.9.Final
Connection pool hibernate-hikaricp (HikariCP) 7.1.9.Final
JDBC driver com.mysql:mysql-connector-j 9.7.0
DB migrations org.flywaydb:flyway-core / flyway-mysql 11.20.3
Error tracking io.sentry:sentry 8.41.0
HTTP client com.squareup.okhttp3:okhttp-jvm 5.3.2
JSON org.json:json + com.google.code.gson:gson —
Classpath scanning org.reflections:reflections 0.10.2
Emoji parsing net.fellbaum:jemoji 1.7.6
Logging Log4j2 + log4j-slf4j2-impl 2.26.0
Testing JUnit Jupiter 6 + Mockito 5 —

The HTTP server for the internal API uses embedded Javalin (with OpenAPI/Swagger plugins); see web-api.md.

Startup lifecycle

The entry point is App.main() followed by the App() constructor which calls startup(). Bootstrap is driven by a hand-rolled ApplicationContext that owns wired instances and starts every Lifecycle component in ascending priority order.

  1. Print banner / read env — reads NETNEUTRALITY_BOT_ENV (default dev) via the early-bootstrap Settings.getInstance() singleton (the only remaining process-wide settings access point; it is also registered into the context as SettingsProvider for constructor injection).
  2. Sentry — if NETNEUTRALITY_SENTRY_DSN is set, initialize the Sentry SDK (environment, release = VERSION, traces sample rate 1.0).
  3. Log levels — in dev, raise JDA and Lavalink client logging to DEBUG.
  4. startup():
    1. Validate NETNEUTRALITY_BOT_TOKEN is present (throws if missing).
    2. buildContext() — register domain services and lifecycle adapters (see priorities below).
    3. context.start() — start every Lifecycle component in ascending priority order.
    4. Register a JVM shutdown hook calling shutdown() → context.stop().

Lifecycle priorities (startup order)

Priority Component What start() does
10 SessionManager (SessionProvider) Build Hibernate SessionFactory + run Flyway migrations. Binds itself into JpaUtils.
20 ThreadManager (ExecutorProvider) No-op start (executors are lazy). Bound into ChannelMessagePurger.
30 CommandManager (CommandRegistry) Reflectively discover/register Command implementations with context-aware constructor injection.
30 TaskManagerImpl (TaskManager) Reflectively load built-in & scheduled tasks (DI) and reload persistable tasks from the DB.
40 MusicServiceImpl (MusicService) Create the shared LavalinkClient, register the node and subscribe to its events.
55 ScheduledTaskManager No-op start (tasks are scheduled by TaskManager during its own start).
60 JDA adapter Build JDA with gateway intents and await ready.
65 Post-JDA adapter Register slash commands, attach EventsHandler (injected providers), set presence.
70 WebApi Start the internal HTTP server (NETNEUTRALITY_API_PORT, default 9000).

Commands and tasks that need shared services declare them as constructor parameters (MusicService, TaskManager, GuildServiceProvider, SettingsProvider, CommandRegistry, …). ReflectionHelper picks the richest public constructor whose parameter types all resolve from the context (falling back to a no-arg constructor when none match).

Gateway intents

initJDA() enables: GUILD_MEMBERS, GUILD_MESSAGE_REACTIONS, GUILD_VOICE_STATES, MESSAGE_CONTENT. It also installs new JDAVoiceUpdateListener(music.getClient()) as the voice dispatch interceptor, so voice server/state updates are forwarded to Lavalink instead of JDA's own audio stack. DAVE (end-to-end encrypted voice) is handled by the node, which is why the bot no longer ships native audio encoders.

Shutdown lifecycle

App.shutdown() (invoked by the JVM shutdown hook, ShutdownCommand, or the HTTP /actions/shutdown endpoint) calls context.stop(), which stops every registered Lifecycle component in the exact reverse of start order. Failures in one component are logged and do not block the others. Effectively this means:

  1. WebApi.stop() — stop the HTTP server and its executor.
  2. Post-JDA adapter — no-op.
  3. JDA adapter — jda.shutdown().
  4. ScheduledTaskManager.stop() — cancel all scheduled tasks.
  5. MusicServiceImpl.stop() — dispose the event subscriptions, destroy the players and close the Lavalink client.
  6. CommandManager / TaskManagerImpl — no-op stop today.
  7. ThreadManager.stop() — shut down the shared executors.
  8. SessionManager.stop() — release the Hibernate registry / connection pool.

Configuration & build

  • Config sources — Settings loads bot.properties and overlays environment variables (env vars win). See utilities-and-bootstrap.md. An example file lives at bot.properties.example.
  • Build — ./mvnw package produces target/netneutrality.jar (Shade fat JAR, main class me.bobert.netneutrality.App). Checkstyle (checkstyle.xml) runs in the test phase and fails the build on violations.
  • Run — the preferred method is Docker Compose (docker compose build && docker compose up -d), which also provisions the MariaDB database. See README.md and docker-compose.yml.

Package map

Package Contents
me.bobert.netneutrality Entry point (App)
…bootstrap ApplicationContext, Lifecycle, BotState, ReflectionHelper
…core.command Command engine (registry, processor, manager, options, impl bases)
…core.context Context interfaces, abstract bases (CommandContext, TaskContext, etc.)
…core.tasks Task engine (manager, subtypes, traits, persistence, generic buttons)
…core.thread Executors & scheduling (ThreadManager, ScheduledTaskManager, BaseThreadFactory)
…core LogHandler
…command.* Concrete command implementations (admin, basic, bot, crypto, custom, git, music, react)
…context.command / …context.tasks Concrete context implementations (slash, message, button, modal, emoji, etc.)
…context MusicChannelContext
…tasks.* Concrete task implementations (git, help, music, reaction)
…db Session/migration, Repository, JpaUtils, GSettings
…db.entities / …db.repository / …db.service Persistence model, repositories, services
…hooks EventsHandler
…http Internal web API and HTTP client module
…music Music domain (service, manager, queue, scheduler, track loading)
…utils Settings / SettingsProvider, ChannelMessagePurger, MusicUtils
…exceptions Custom exception hierarchy