Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/mysql-session-service-integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: MySQL session service integration

on:
pull_request:
branches:
- main
paths:
- "contrib/mysql-session-service/**"
- "core/**"
- "pom.xml"
- ".mvn/**"
- ".github/workflows/mysql-session-service-integration.yml"
- "mvnw"
- "mvnw.cmd"

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
mysql-integration:
runs-on: ubuntu-24.04
timeout-minutes: 20
strategy:
matrix:
mysql-image: ["mysql:8.0", "mysql:8.4"]

steps:
- name: Checkout code
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false

- name: Set up Java 17
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
with:
distribution: temurin
java-version: "17"
cache: maven

- name: Install ADK core without tests
run: ./mvnw -pl core -am -DskipTests install

- name: Run MySQL unit and container integration tests
run: ./mvnw -pl contrib/mysql-session-service -Pintegration-tests -Dmysql.image=${{ matrix.mysql-image }} verify
117 changes: 117 additions & 0 deletions contrib/mysql-session-service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# MySQL Session Service for ADK

This module provides a MySQL-backed implementation of ADK's `BaseSessionService`. It persists
sessions, events, and app- and user-scoped state while keeping the MySQL JDBC driver and connection
pool under the application's control.

## Dependency

Use the same version for ADK core and this module. The MySQL driver and a production connection pool
are intentionally not forced on consumers; add the implementations used by your application.

<!-- x-release-please-released-start-version -->

```xml
<properties>
<adk.version>1.12.0</adk.version>
</properties>
```

<!-- x-release-please-released-end -->

```xml
<dependencies>
<dependency>
<groupId>com.google.adk</groupId>
<artifactId>google-adk</artifactId>
<version>${adk.version}</version>
</dependency>
<dependency>
<groupId>com.google.adk</groupId>
<artifactId>google-adk-mysql-session-service</artifactId>
<version>${adk.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.6.0</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>7.0.2</version>
</dependency>
</dependencies>
```

The version above tracks ADK releases; until this contribution is released, build this module and
ADK core together from the PR branch and use its snapshot version. Connector/J 9.6 retains support
for MySQL 8.0. Applications using newer drivers should check their minimum supported server version.

## Database schema

MySQL 8.0 or newer is required. Apply the packaged
[`mysql-session-service-schema.sql`](src/main/resources/mysql-session-service-schema.sql) before
constructing the service.

The session identity is the ADK `SessionKey` tuple `(app_name, user_id, session_id)`, so the same
client-generated session ID can safely be reused in a different app or user scope. Events use an
internal monotonic sequence to provide deterministic ordering when multiple events have the same
millisecond timestamp.

The schema in earlier preview revisions of this contribution used globally unique `session_id` and
`event_id` primary keys. Recreate or migrate those tables before upgrading to this schema.

## Usage

Configure a `DataSource`, construct the service, and pass it to the current `Runner` builder.

```java
import com.google.adk.runner.Runner;
import com.google.adk.sessions.MySqlSessionService;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/my_db");
config.setUsername("user");
config.setPassword("password");
HikariDataSource dataSource = new HikariDataSource(config);

MySqlSessionService sessionService = new MySqlSessionService(dataSource);
Runner runner =
Runner.builder()
.agent(myAgent)
.appName("my-app")
.sessionService(sessionService)
.build();
```

App-prefixed (`app:`) and user-prefixed (`user:`) values supplied when creating a session are
persisted in their respective shared state scopes. Temporary (`temp:`) values are returned on the
newly created session but are intentionally not persisted and therefore do not appear after a
reload.

`appendEvent` is lazy: subscribe to the returned `Single` (or use `blockingGet`) to persist the event.
It updates the caller's session state and event list only after the database transaction commits.
A stale session fails with `ConcurrentModificationException`; reload it before retrying.

## Testing

Unit and contract tests use H2 in MySQL compatibility mode and require no external services:

```bash
./mvnw -pl contrib/mysql-session-service -am test
```

The integration profile runs the `*IT` tests against a real MySQL container and therefore requires
Docker:

```bash
./mvnw -pl contrib/mysql-session-service -am -Pintegration-tests verify
```

The same contract tests run against H2 and MySQL, including event JSON round trips, scoped state,
timestamp precision, and rollback without mutating the caller's session. The container defaults to
MySQL 8.4; select MySQL 8.0 with `-Dmysql.image=mysql:8.0`. A running Docker Engine compatible with
Testcontainers 2.x is required.
143 changes: 143 additions & 0 deletions contrib/mysql-session-service/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2025 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.google.adk</groupId>
<artifactId>google-adk-parent</artifactId>
<version>1.12.1-SNAPSHOT</version><!-- {x-version-update:google-adk:current} -->
<relativePath>../../pom.xml</relativePath>
</parent>

<artifactId>google-adk-mysql-session-service</artifactId>
<name>Agent Development Kit - MySQL Session Management</name>
<description>MySQL integration with Agent Development Kit for User Session Management</description>

<properties>
<testcontainers.version>2.0.5</testcontainers.version>
<!-- Connector/J 9.6 retains MySQL 8.0 support; newer releases require MySQL 8.4. -->
<mysql-connector.version>9.6.0</mysql-connector.version>
<mysql.image>mysql:8.4</mysql.image>
<mockito.javaagent.arg>"-javaagent:${org.mockito:mockito-core:jar}"</mockito.javaagent.arg>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>${testcontainers.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>com.google.adk</groupId>
<artifactId>google-adk</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-connector.version}</version>
<scope>provided</scope>
</dependency>
Comment thread
voborl00 marked this conversation as resolved.

<!-- Testing -->
<dependency>
<groupId>com.google.truth</groupId>
<artifactId>truth</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-mysql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<profiles>
<profile>
<id>integration-tests</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.5.2</version>
<configuration>
<argLine>${surefire.argLine}</argLine>
<systemPropertyVariables>
<mysql.image>${mysql.image}</mysql.image>
</systemPropertyVariables>
</configuration>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Loading
Loading