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
61 changes: 54 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,26 +48,40 @@ Then reference it in your project's `pom.xml`:

## Architecture

The library follows a layered architecture:
The library follows a layered architecture with constructor-based dependency injection. The public service interfaces describe application use cases, service implementations orchestrate those use cases, domain ports isolate request execution, and infrastructure abstractions model low-level request handling.

```text
pl.vtt.wpi.core
├── application
│ ├── config # AuthorizationHolder (thread-local auth state)
│ ├── context # ApplicationServices facade + lazy composition root
│ ├── exception # Application-level exceptions
│ └── service
│ ├── ... # Service interfaces (LoginService, RuntimeDataService, etc.)
│ └── impl # Internal service implementations
│ └── impl # Service implementations wired through constructors
├── domain
│ ├── dto
│ ├── model
│ └── port # Input/Output port contracts + endpoint-specific port implementations
│ ├── dto # Request DTOs used by application services and ports
│ ├── model # Domain values and device state models
│ └── port # Input/Output contracts + endpoint-specific port implementations
└── infrastructure
├── Request / Response
├── RequestFactory / RequestHandler / RequestSender
└── factory # SynchronizedRequestFactory
```

### Layer responsibilities

| Layer | Responsibility |
|---|---|
| `application.service` | Stable use-case API exposed to library consumers. |
| `application.service.impl` | Use-case orchestration, validation, exception mapping, and constructor-injected dependencies. |
| `application.context` | Optional composition helper that lazily creates default service implementations from supplied ports. |
| `domain.model` / `domain.dto` | Shared domain values, device data records, credentials, users, and request DTOs. |
| `domain.port` | Generic `InputPort` / `OutputPort` contracts and endpoint-specific port implementations. |
| `infrastructure` | Request/response abstractions and request factory/sender/handler contracts used by ports. |

Application services do not create their own ports. Instead, callers either instantiate service implementations directly with constructor injection or use `LazyApplicationServices` as a small composition root. `LazyApplicationServices` accepts `Supplier` instances for all required ports, resolves each supplier only when a dependent service is first requested, and then reuses the created service instance.

## Key Concepts

### Authentication
Expand Down Expand Up @@ -116,7 +130,7 @@ Request<T> request = new Request<>(
);
```

### Application Services
### Application Services and lazy composition

The package `pl.vtt.wpi.core.application.service` currently exposes interfaces for:

Expand All @@ -130,7 +144,40 @@ The package `pl.vtt.wpi.core.application.service` currently exposes interfaces f
- `DebugService`
- `RebootService`

Concrete implementations are provided in `pl.vtt.wpi.core.application.service.impl`.
Concrete implementations are provided in `pl.vtt.wpi.core.application.service.impl`. They use constructor injection, so tests and applications can provide fake or real `InputPort` / `OutputPort` implementations explicitly.

For applications that want a single access point, `pl.vtt.wpi.core.application.context` provides:

- `ApplicationServices` — a facade exposing getters for all service interfaces.
- `LazyApplicationServices` — a builder-based implementation that creates service implementations on first use.
- `Lazy<T>` — a thread-safe memoizing supplier used internally by the composition root.

Example composition:

```java
ApplicationServices services = LazyApplicationServices.builder()
.authOutputPort(() -> authOutputPort)
.adminPasswordResetInputPort(() -> adminPasswordResetInputPort)
.usersOutputPort(() -> usersOutputPort)
.userCreateRequestInputPort(() -> userCreateRequestInputPort)
.changePasswordInputPort(() -> changePasswordInputPort)
.removeUserInputPort(() -> removeUserInputPort)
.runtimeDataOutputPort(() -> runtimeDataOutputPort)
.pixelProgramsOutputPort(() -> pixelProgramsOutputPort)
.runtimeDataInputPort(() -> runtimeDataInputPort)
.pixelProgramsInputPort(() -> pixelProgramsInputPort)
.wifiConfigInputPort(() -> wifiConfigInputPort)
.deviceInfoOutputPort(() -> deviceInfoOutputPort)
.logsOutputPort(() -> logsOutputPort)
.logsDeleteInputPort(() -> logsDeleteInputPort)
.currentStateOutputPort(() -> currentStateOutputPort)
.rebootInputPort(() -> rebootInputPort)
.build();

RuntimeDataService runtimeDataService = services.runtimeDataService();
```

Only `runtimeDataService`, `runtimeDataOutputPort`, `pixelProgramsOutputPort`, and `runtimeDataInputPort` are resolved by the final line in this example; unrelated ports remain uninitialized until another service is requested.

### Domain Ports

Expand Down
1 change: 1 addition & 0 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module wpi.core {
exports pl.vtt.wpi.core.application.context;
exports pl.vtt.wpi.core.application.exception;
exports pl.vtt.wpi.core.application.service;
exports pl.vtt.wpi.core.domain.dto;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package pl.vtt.wpi.core.application.context;

import pl.vtt.wpi.core.application.service.AdminPasswordService;
import pl.vtt.wpi.core.application.service.DebugService;
import pl.vtt.wpi.core.application.service.DeviceInfoService;
import pl.vtt.wpi.core.application.service.LoginService;
import pl.vtt.wpi.core.application.service.NetworkConfigurationService;
import pl.vtt.wpi.core.application.service.PixelProgramService;
import pl.vtt.wpi.core.application.service.RebootService;
import pl.vtt.wpi.core.application.service.RuntimeDataService;
import pl.vtt.wpi.core.application.service.UserManagementService;

public interface ApplicationServices {
LoginService loginService();

AdminPasswordService adminPasswordService();

UserManagementService userManagementService();

RuntimeDataService runtimeDataService();

PixelProgramService pixelProgramService();

NetworkConfigurationService networkConfigurationService();

DeviceInfoService deviceInfoService();

DebugService debugService();

RebootService rebootService();
}
36 changes: 36 additions & 0 deletions src/main/java/pl/vtt/wpi/core/application/context/Lazy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package pl.vtt.wpi.core.application.context;

import java.util.Objects;
import java.util.function.Supplier;

public final class Lazy<T> implements Supplier<T> {
private volatile Supplier<? extends T> initializer;
private volatile T value;

private Lazy(Supplier<? extends T> initializer) {
this.initializer = Objects.requireNonNull(initializer, "initializer cannot be null");
}

public static <T> Lazy<T> of(Supplier<? extends T> initializer) {
return new Lazy<>(initializer);
}

@Override
public T get() {
Supplier<? extends T> currentInitializer = initializer;
if (currentInitializer == null) {
return value;
}
synchronized (this) {
currentInitializer = initializer;
if (currentInitializer == null) {
return value;
}
T current = Objects.requireNonNull(currentInitializer.get(),
"initializer cannot return null");
value = current;
initializer = null;
return current;
}
}
}
Loading
Loading