The bridge between your Domain and your Data.
Storage is a Java library built to simplify the implementation of the Repository Pattern. It allows developers to integrate persistence into Domain-Driven Design (DDD) architectures without coupling the business logic to specific database technologies.
With Storage, you define what needs to be saved in your Domain Layer and let this library handle how it is saved in the Infrastructure Layer.
- DDD Friendly: Keeps your Domain Entities pure and your Repository Interfaces clean.
- Infrastructure Agnostic: Switch between Flat Files (JSON/YAML) or InMemory (Map/Caffeine) storage without changing a single line of business logic.
- Decoupled: Acts as an adapter layer, preventing database dependencies from leaking into your application core.
- Simple API: Provides a fluent and consistent API for standard CRUD operations (Sync and Async).
Currently, this library is in development. To use it, you must select the distribution module that fits your needs (e.g., Gson, Yaml, Caffeine).
Since it is not yet on Maven Central, you must build it locally:
# 1. Clone the repository
git clone https://github.com/emptyte-team/storage.git
# 2. Navigate to the folder
cd storage
# 3. Publish to your local Maven repository
./gradlew publishToMavenLocalOnce installed locally, add mavenLocal() to your repositories and include the dependency for the specific implementation you want to use.
β οΈ Important: Do not depend on the core directly. Choose a distribution artifact ending in-dist.
repositories {
mavenLocal() // Important: Look in the local .m2 folder first
mavenCentral()
}
dependencies {
// Replace 'TYPE' with the storage implementation you need (e.g., gson-dist)
// Replace 'VERSION' with the current version (e.g., 0.1.0)
implementation("team.emptyte:storage-gson-dist:VERSION")
// or
implementation("team.emptyte:storage-caffeine-dist:VERSION")
}<repositories>
<repository>
<id>local-maven</id>
<url>file://${user.home}/.m2/repository</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>team.emptyte</groupId>
<artifactId>storage-gson-dist</artifactId>
<version>VERSION</version>
</dependency>
</dependencies>
Here is how Storage fits into a layered architecture:
Defines the rules. No libraries or frameworks here (except simple annotations if needed).
import team.emptyte.storage.domain.AggregateRoot;
public class UserAggregateRoot extends AggregateRoot {
private final String name;
public UserAggregateRoot(final String id, final String name) {
super(id);
this.name = name;
}
public String name() {
return this.name;
}
}Orchestrates logic using the Repository Interface provided by Storage.
import repository.team.emptyte.storage.domain.AsyncAggregateRootRepository;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class UserService {
private final AsyncAggregateRootRepository<UserAggregateRoot> repository;
// Dependency Injection via Constructor
public UserService(final AsyncAggregateRootRepository<UserAggregateRoot> repository) {
this.repository = repository;
}
public CompletableFuture<Void> create(final String name) {
final String id = UUID.randomUUID().toString();
final UserAggregateRoot user = new UserAggregateRoot(id, name);
return this.repository.saveAsync(user)
.thenAccept(savedUser -> System.out.println("User created: " + savedUser.name()))
.exceptionally(ex -> {
System.err.println("Error creating user: " + ex.getMessage());
return null;
});
}
}This is where you decide which implementation to use (Gson, YAML, etc.) and inject dependencies.
// Example import assuming you used 'storage-gson-dist'
import repository.team.emptyte.storage.domain.AsyncAggregateRootRepository;
import team.emptyte.storage.infrastructure.gson.YamlAggregateRootRepository;
import codec.team.emptyte.storage.codec.TypeAdapter;
import com.google.gson.JsonObject;
import java.nio.file.Path;
import java.util.UUID;
import java.util.concurrent.Executor;
public class Main {
static void main(final String[] args) {
// 1. Instantiate the concrete repository (depends on the -dist library you imported)
// Hypothetical example using Gson:
final Executor executor = Executors.newSingleThreadExecutor();
final Path folder = Paths.get("data");
final AsyncAggergateRootRespository<UserAggregateRoot> repository = GsonAggregateRootRepository.<UserAggregateRoot>builder()
.folder(folder)
.prettyPrinting(true)
.typeAdapter(new TypeAdapter<UserAggregateRoot, JsonObject>() {
public @NotNull JsonObject write(final @NotNull UserAggregateRoot value) {
final JsonObject serialized = new JsonObject();
serialized.addProperty("id", value.id());
serialized.addProperty("name", value.name());
return serialized;
}
public @NotNull UserAggregateRoot read(final @NotNull JsonObject value) {
final String id = value.get("id").getAsString();
final String name = value.get("name").getAsString();
return new UserAggregateRoot(id, name);
}
})
.build(executor);
// 2. Inject a repository into the service
final UserService userService = new UserService(repository);
// 3. Run logic
userService.create("Nelson Rodriguez");
}
}Contributions are welcome! If you want to add support for a new database type (e.g., storage-redis-dist) or improve the core logic:
- Fork the repository.
- Create your feature branch (
git checkout -b feature/AmazingFeature). - Commit your changes (
git commit -m 'Add some AmazingFeature'). - Push to the branch (
git push origin feature/AmazingFeature). - Open a Pull Request.
Distributed under the MIT License. See LICENSE for more information.