ark_data_layer 0.1.0-dev.1
ark_data_layer: ^0.1.0-dev.1 copied to clipboard
Pure Dart foundations for repositories, data sources, and observable business data.
Ark Data Layer #
Pure Dart foundations for concrete repository implementations that own current
business data and coordinate typed DataSource contracts.
Ark Data Layer provides a narrow reusable base for the data layer of a Clean
Architecture application. Domain code remains free to declare its own
repository interfaces. A concrete data-layer implementation extends
Repository<Data>, implements the domain interface, resolves its technical
sources once, and publishes committed business data to interested consumers.
0.1.0-dev.1is a prerelease. The core responsibilities are defined, but public API details may still change before the first stable version.
Why this package exists #
Clean Architecture defines the repository boundary but intentionally leaves many runtime details open. Applications still need consistent answers to practical questions:
- Where does the current business data live?
- How does a repository receive several local or remote data sources?
- When is a missing source reported?
- How do background application processes observe committed data?
- Who closes repositories, subscriptions, database clients, and API clients?
Ark Data Layer makes those decisions explicit without becoming an ORM, a networking library, a cache, an event store, or a dependency-injection container.
Core model #
Domain layer
└── declares a business repository interface
Data layer
├── declares typed DataSource contracts
├── implements technical sources
└── extends Repository<Data> and implements the domain interface
Application layer
└── reads data or subscribes to Repository.stream
Composition scope
└── constructs and closes DataSources, repositories, and subscriptions
The public responsibilities are deliberately small:
DataSourcemarks a technical source contract.DataSourceContainerstores an immutable set of already constructed sources and resolves exactly one implementation of a requested contract.Repository<Data>owns the current business data and its observable lifecycle.
Installation #
dependencies:
ark_data_layer: ^0.1.0-dev.1
Import the public library:
import 'package:ark_data_layer/ark_data_layer.dart';
Define DataSource contracts #
DataSource is a marker interface. Application-specific contracts extend it
and expose only the technical operations needed by repositories.
abstract interface class UserRemoteDataSource implements DataSource {
Future<List<UserDto>> fetchUsers();
}
abstract interface class UserCacheDataSource implements DataSource {
Future<List<UserDto>> readUsers();
Future<void> writeUsers(List<UserDto> users);
}
Use distinct contract types for distinct responsibilities. A remote source and a cache should not be selected through string qualifiers when their behavior is different.
Declare the domain repository interface #
The domain interface belongs to the application, not to Ark Data Layer:
abstract interface class UserRepository {
UsersData get data;
Stream<UsersData> get stream;
Future<void> refresh();
}
Domain code does not need to expose DataSourceContainer or depend on a
specific HTTP, database, or storage implementation.
Implement the repository #
Resolve required sources in the concrete constructor. Configuration errors then fail while the object graph is being built rather than during a business operation.
final class UserRepositoryImpl extends Repository<UsersData>
implements UserRepository {
UserRepositoryImpl({
required DataSourceContainer dataSources,
}) : _remote = dataSources.get<UserRemoteDataSource>(),
_cache = dataSources.get<UserCacheDataSource>(),
super(
initialData: const UsersData.empty(),
dataSources: dataSources,
);
final UserRemoteDataSource _remote;
final UserCacheDataSource _cache;
@override
Future<void> refresh() async {
final List<UserDto> records = await _remote.fetchUsers();
await _cache.writeUsers(records);
setData(UsersData.fromDtos(records));
}
}
setData replaces and publishes a value. updateData performs a synchronous
read-transform-commit operation:
updateData(
(current) => current.copyWith(selectedUserId: userId),
);
Keep Data immutable. Returning a mutable collection from data would allow
external code to alter repository state without a commit or notification.
Build the DataSourceContainer #
The container receives already constructed objects:
final DataSourceContainer dataSources = DataSourceContainer(<DataSource>[
HttpUserRemoteDataSource(client),
SqliteUserCacheDataSource(database),
]);
final UserRepository repository = UserRepositoryImpl(
dataSources: dataSources,
);
Resolution is strict:
- no matching implementation:
DataSourceNotFoundException; - exactly one matching implementation: returned as the requested type;
- more than one matching implementation:
AmbiguousDataSourceException.
get<T>() never silently chooses the first match. maybeGet<T>() returns
null only when no source matches and still rejects ambiguity.
The container is not a general service locator. Do not place repositories, UseCases, presentation objects, or arbitrary services in it.
Observe repository data #
Every repository exposes a replay-latest broadcast stream:
final StreamSubscription<UsersData> subscription = repository.stream.listen(
(data) {
// React to this committed value.
},
);
Observable guarantees:
- a new subscriber receives the current value first;
- committed values are delivered asynchronously in commit order;
- each explicit commit is published, even when the new value compares equal;
- subscriptions are independent and may be paused, resumed, or cancelled;
- the stream completes when the repository closes;
- a subscription created after close completes without replay;
dataremains readable after close.
The stream represents current repository state. It is not a durable domain event log and does not promise exactly-once processing. Use an event store or outbox when every transition must survive process termination and be handled exactly once.
Lifecycle and ownership #
Close application bindings before the objects they connect:
await bindingSubscription.cancel();
await useCase.close();
await repository.close();
await remoteDataSource.close();
await cacheDataSource.close();
The recommended ownership order is:
- the composition scope constructs DataSources;
- it passes them through a repository-specific
DataSourceContainer; - a repository owns subscriptions or resources it creates itself;
- the container never closes its contents;
Repository.close()invokescloseRepository()once and then completes the repository stream;- the composition scope closes shared DataSources after repositories.
After closing starts, setData and updateData throw
RepositoryClosedException. Repeated close() calls return the same Future.
UseCase Forge integration #
Ark Data Layer does not depend on UseCase Forge. An application-level binding can connect both packages without coupling either core library to the other:
final subscription = repository.stream.skip(1).listen(
(data) => useCase.add(UserRepositoryDataChanged(data)),
);
The listener should submit a Command, not invoke business processing
directly. The update then follows the regular UseCase Forge admission,
scheduling, cancellation, history, diagnostics, and error lifecycle.
The binding owns the subscription. A DI or composition scope creates the binding only after the repository and UseCase are ready, and cancels it before closing either object.
See the complete application integration guide.
Public API #
| API | Responsibility |
|---|---|
DataSource |
Marker for a technical source contract |
DataSourceContainer |
Immutable, strict resolution of repository sources |
Repository<Data> |
Current data, observable commits, and repository lifecycle |
DataSourceNotFoundException |
Missing source configuration |
AmbiguousDataSourceException |
Multiple sources match one requested contract |
RepositoryClosedException |
Mutation attempted after closing starts |
Documentation and project #
- Runnable example
- Architecture and boundaries
- Stream and lifecycle contract
- UseCase Forge integration
- Changelog
- Contributing
- Security policy
- GitLab work items
- Verified ArkTelos publisher
- ArkTelos
License #
Licensed under the Apache License, Version 2.0. See LICENSE and NOTICE.