chassis 0.0.1+4
chassis: ^0.0.1+4 copied to clipboard
An architectural framework for flutter applications
Chassis 🏎️ #
An opinionated architectural framework for Flutter that provides a solid foundation for professional, scalable, and maintainable applications.
Rigid in Structure, Flexible in Implementation.
Chassis guides your project's structure by combining the clarity of MVVM with a pragmatic, front-end friendly implementation of CQRS principles. It's designed to make best practices the easiest path forward.
Learn more from the full documentation.
Why Use Chassis? #
- 🏛️ Structure by Design: Don't rely on developer discipline to maintain a clean codebase. Chassis enforces a clean data flow, making the code intuitive and organized by default.
- 🧠 Explicit Logic: By separating Commands and Queries, your business logic becomes explicit, discoverable, and easier to reason about.
- ✅ Testability First: Every layer is decoupled and designed to be easily testable in isolation, from business logic handlers to your data layer.
- 🔌 Observable & Pluggable: Easily plug-in middleware to observe every Command and Query in your application for logging, analytics, or debugging.
The Chassis Ecosystem #
Chassis is designed as a modular set of packages to enforce a strong separation between business logic and UI.
chassis
(this package): The core, pure Dart library. It contains the foundational building blocks (Mediator
,Command
,Query
, etc.) and has no dependency on Flutter. This is where all your application's business logic lives.chassis_flutter
: Provides Flutter-specific widgets and helpers to integrate the corechassis
logic by following theMVVM
pattern.
Core Concepts #
Chassis is built around the Command Query Responsibility Segregation (CQRS) pattern, adjusted for front-end development needs. Fundamentally, this means separating the act of writing data from reading data.
-
Writes (Commands): Any operation that mutates domain state (as opposite to view state) is a Command. Commands are objects representing an intent to change something (e.g., CreateUserCommand). They are processed by a single handler containing all the necessary business logic and validation, which ensures data consistency and integrity.
-
Reads (Queries): All data retrieval is done through Queries. A query asks for information and returns a domain object but is strictly forbidden from changing state.
These messages are routed through a central Mediator, which decouples the sender from the handler. This design provides a clear separation of concerns, enhances scalability, and simplifies complex business domains.
The Flow of Action (Commands) 🎬
When you need to change the application's state, you send a Command
.
ViewModel ➡️ Command ➡️ Mediator ➡️ Handler ➡️ Data Layer
The Flow of Data (Queries) 📊
When you need to read or subscribe to data, you send a Query
.
ViewModel ➡️ Query ➡️ Mediator ➡️ Handler ➡️ Data Layer ➡️ Returns Data
Core API in Action #
This example demonstrates the fundamental pattern of defining and handling a message. Note that this code is pure Dart and lives in your core logic, completely independent of Flutter.
1. Define a Query
A Query is an immutable message describing the data you want.
// domain/use_cases/get_greeting_query.dart
import 'package:chassis/chassis.dart';
// Implement ReadQuery for one-time data fetches, or WatchQuery for streams.
class GetGreetingQuery implements ReadQuery<String> {
const GetGreetingQuery();
}
2. Create the Handler
A Handler contains the business logic to process the Query.
// app/use_cases/get_greeting_query_handler.dart
import 'package:chassis/chassis.dart';
// Each message type has a corresponding handler:
// ReadQuery -> ReadHandler
// WatchQuery -> WatchHandler
// Command -> CommandHandler
class GetGreetingQueryHandler implements ReadHandler<GetGreetingQuery, String> {
final IGreetingRepository greetingRepository;
GetGreetingQueryHandler({
required this.greetingRepository,
});
@override
Future<String> read(GetGreetingQuery query) {
// Your business logic lives here
return greetingRepository.getGreeting();
}
}
3. Register and Dispatch with the Mediator
At your application's startup, register your handler. Then, from your application logic, dispatch the query to get data.
// At application startup
final mediator = Mediator();
final greetingRepository = GreetingRepository();
mediator.registerQueryHandler(
GetGreetingQueryHandler(greetingRepository: greetingRepository),
);
// From your application layer
final String greeting = await mediator.read(const GetGreetingQuery());
print(greeting); // Outputs the result from your repository
Next Steps #
You've now seen the core pattern of the chassis
library. To see how to integrate this logic with your Flutter UI, please check out:
- The full documentation for advanced concepts, tutorials, and best practices.
- The
chassis_flutter
package to connect your logic to widgets.