Перейти к содержимому

InMemory транспорт

InMemory транспорт - это самый производительный транспорт RPC Dart, предназначенный для коммуникации внутри одного процесса. Он обеспечивает передачу объектов без копирования, что делает его идеальным для высокопроизводительных приложений, тестирования и сценариев, где требуется максимальная производительность.

Производительность без копирования

Объекты передаются по ссылке без накладных расходов на сериализацию, обеспечивая максимальную производительность для передачи больших данных.

Типобезопасность

Полная информация о типах Dart сохраняется через границы RPC, поддерживая безопасность на этапе компиляции.

Идеально для тестирования

Идеально подходит для модульных и интеграционных тестов, где нужна быстрая, предсказуемая коммуникация.

Эффективность памяти

Отсутствие дублирования данных или сериализации означает минимальный отпечаток в памяти.

Самое значительное преимущество InMemory транспорта - передача объектов без копирования:

class LargeDataSet {
final List<ComplexObject> items = List.generate(
100000,
(i) => ComplexObject(id: i, data: List.filled(1000, i)),
);
}
// С InMemory транспортом весь этот объект передаётся по ссылке
// Никакой сериализации, никакого копирования, только прямая передача ссылки!
final result = await service.processLargeDataSet(LargeDataSet());

В отличие от сетевых транспортов, которые требуют сериализации, InMemory транспорт сохраняет полную информацию о типах Dart:

// Сложные типы работают беспрепятственно
class User {
final String id;
final DateTime createdAt;
final Set<String> permissions;
final Map<String, dynamic> metadata;
User({
required this.id,
required this.createdAt,
required this.permissions,
required this.metadata,
});
}
// Типы сохраняются в точности такими, какие они есть
final user = await userService.getUser('123');
// user имеет точно тип User, а не Map или восстановленный объект

InMemory транспорт работает с парами транспортов - соединённым клиентским и серверным транспортом:

// Создаём пару соединённых транспортов
final (clientTransport, serverTransport) = RpcInMemoryTransport.pair();
// Настраиваем серверный эндпоинт
final responder = RpcResponderEndpoint(transport: serverTransport);
responder.registerServiceContract(CalculatorResponder());
responder.start();
// Настраиваем клиентский эндпоинт
final caller = RpcCallerEndpoint(transport: clientTransport);
final calculator = CalculatorCaller(caller);
// Делаем RPC вызовы
final result = await calculator.add(AddRequest(10, 5));
print('Результат: ${result.result}'); // Результат: 15

Вот полный пример, показывающий InMemory транспорт в действии:

calculator_contract.dart
abstract interface class ICalculatorContract {
static const name = 'Calculator';
static const methodAdd = 'add';
static const methodSubtract = 'subtract';
static const methodMultiply = 'multiply';
static const methodDivide = 'divide';
}
class MathRequest {
final double a;
final double b;
MathRequest(this.a, this.b);
}
class MathResponse {
final double result;
MathResponse(this.result);
}

Для приложений, которым нужна максимальная производительность:

class DataProcessor {
Future<ProcessedData> processLargeDataset(LargeDataset data) async {
// С InMemory транспортом весь набор данных передаётся по ссылке
// Никаких накладных расходов на сериализацию для миллионов записей
return ProcessedData(
results: data.records.map((record) => processRecord(record)).toList(),
);
}
}

InMemory транспорт идеален для тестирования:

void main() {
group('Тесты сервиса калькулятора', () {
late RpcResponderEndpoint responder;
late RpcCallerEndpoint caller;
late CalculatorCaller calculator;
setUp(() async {
final (clientTransport, serverTransport) = RpcInMemoryTransport.pair();
responder = RpcResponderEndpoint(transport: serverTransport);
responder.registerServiceContract(CalculatorResponder());
responder.start();
caller = RpcCallerEndpoint(transport: clientTransport);
calculator = CalculatorCaller(caller);
});
tearDown(() async {
await caller.close();
await responder.close();
});
test('должен правильно складывать числа', () async {
final result = await calculator.add(MathRequest(2, 3));
expect(result.result, equals(5));
});
test('должен обрабатывать деление на ноль', () async {
expect(
() => calculator.divide(MathRequest(10, 0)),
throwsA(isA<RpcException>()),
);
});
});
}

Для приложений, где все сервисы выполняются в одном процессе:

void main() async {
// Создаём пары транспортов для различных сервисов
final (userClientTransport, userServerTransport) = RpcInMemoryTransport.pair();
final (orderClientTransport, orderServerTransport) = RpcInMemoryTransport.pair();
final (paymentClientTransport, paymentServerTransport) = RpcInMemoryTransport.pair();
// Настраиваем все responder'ы сервисов
final userResponder = RpcResponderEndpoint(transport: userServerTransport);
userResponder.registerServiceContract(UserServiceResponder());
final orderResponder = RpcResponderEndpoint(transport: orderServerTransport);
orderResponder.registerServiceContract(OrderServiceResponder(
userService: UserServiceCaller(RpcCallerEndpoint(transport: userClientTransport)),
paymentService: PaymentServiceCaller(RpcCallerEndpoint(transport: paymentClientTransport)),
));
final paymentResponder = RpcResponderEndpoint(transport: paymentServerTransport);
paymentResponder.registerServiceContract(PaymentServiceResponder());
// Запускаем все сервисы
await Future.wait([
userResponder.start(),
orderResponder.start(),
paymentResponder.start(),
]);
// Сервисы теперь могут общаться без накладных расходов на сериализацию
}

InMemory транспорт поддерживает все паттерны потоковой передачи RPC с производительностью без копирования:

// Responder
Stream<NumberData> getNumbers(NumberRequest request, {RpcContext? context}) async* {
for (int i = 1; i <= request.count; i++) {
// Каждый объект NumberData передаётся по ссылке
yield NumberData(
value: i,
metadata: {'generated_at': DateTime.now()},
complexData: generateComplexData(i),
);
await Future.delayed(Duration(milliseconds: 100));
}
}
// Caller
Stream<NumberData> getNumbers(NumberRequest request) {
return callServerStream<NumberRequest, NumberData>(
methodName: 'getNumbers',
request: request,
);
}
// Использование
await for (final numberData in calculator.getNumbers(NumberRequest(count: 10))) {
// numberData сохраняет полную информацию о типах и сложные вложенные объекты
print('Число: ${numberData.value}, Сложные данные: ${numberData.complexData}');
}
// Responder
Future<SumResult> sumNumbers(Stream<NumberData> numbers, {RpcContext? context}) async {
double sum = 0;
int count = 0;
await for (final numberData in numbers) {
// Каждый объект numberData приходит с полной информацией о типах
sum += numberData.value;
count++;
}
return SumResult(total: sum, count: count);
}
// Caller
Future<SumResult> sumNumbers(Stream<NumberData> numbers) {
return callClientStream<NumberData, SumResult>(
methodName: 'sumNumbers',
requests: numbers,
);
}
// Responder
Stream<ProcessedData> processStream(Stream<RawData> input, {RpcContext? context}) async* {
await for (final rawData in input) {
// Обрабатываем сырые данные (объекты передаются по ссылке)
final processed = ProcessedData(
id: rawData.id,
processedAt: DateTime.now(),
result: processComplexData(rawData),
);
yield processed;
}
}
// Caller
Stream<ProcessedData> processStream(Stream<RawData> input) {
return callBidirectionalStream<RawData, ProcessedData>(
methodName: 'processStream',
requests: input,
);
}

InMemory транспорт обеспечивает исключительную производительность:

ОперацияInMemoryHTTPWebSocket
Простой RPC~0.01мс~5-10мс~2-5мс
Большой объект (1МБ)~0.01мс~50-100мс~20-40мс
Потоки (1000 элементов)~5мс~1-2с~0.5-1с
// Сравнение передачи больших объектов
class LargeData {
final List<ComplexObject> items = List.generate(10000, (i) => ComplexObject(i));
}
// InMemory транспорт: ~0 дополнительной памяти (передача ссылок)
// Сетевые транспорты: ~2x использование памяти (оригинал + сериализованная копия)

InMemory транспорт работает только внутри одного процесса Dart:

// ✅ Это работает - тот же процесс
final (client, server) = RpcInMemoryTransport.pair();
// ❌ Это не работает - разные процессы
// Нельзя использовать InMemory транспорт через границы процессов

Нельзя использовать для распределённых систем:

// ❌ Нельзя использовать InMemory для микросервисов
// Используйте HTTP, WebSocket или другие сетевые транспорты
// ✅ Используйте для монолитных приложений
final (userClient, userServer) = RpcInMemoryTransport.pair();
final (orderClient, orderServer) = RpcInMemoryTransport.pair();

Объекты, передаваемые через InMemory транспорт, разделяют ссылки:

class MutableData {
List<String> items = [];
}
// ⚠️ Будьте осторожны с изменяемыми объектами
final data = MutableData();
data.items.add('initial');
final result = await service.processData(data);
// Исходный объект может быть изменён сервисом
// Это одновременно преимущество (производительность) и соображение (побочные эффекты)

1. Используйте для разработки и тестирования

Заголовок раздела «1. Используйте для разработки и тестирования»
// Идеально для модульных тестов
void main() {
group('Тесты сервиса', () {
late ServiceCaller service;
setUp(() async {
final (client, server) = RpcInMemoryTransport.pair();
// Настройка эндпоинтов...
service = ServiceCaller(caller);
});
test('быстрые и надёжные тесты', () async {
final result = await service.performOperation(data);
expect(result, meets(criteria));
});
});
}

2. Используйте для высокопроизводительных сценариев

Заголовок раздела «2. Используйте для высокопроизводительных сценариев»
// Идеально для CPU-интенсивной обработки
class ImageProcessor {
Future<ProcessedImage> processImage(RawImageData data) async {
// Большие данные изображения передаются по ссылке - без накладных расходов на копирование
return ProcessedImage(
processed: applyFilters(data),
metadata: generateMetadata(data),
);
}
}
// Используйте InMemory для внутренних сервисов, сетевые транспорты для внешних
class HybridApplication {
void setup() {
// Внутренняя высокопроизводительная коммуникация
final (cacheClient, cacheServer) = RpcInMemoryTransport.pair();
setupCacheService(cacheServer);
// Внешняя API коммуникация
final httpTransport = RpcHttpTransport(url: 'https://api.external.com');
setupExternalApiClient(httpTransport);
}
}
try {
final result = await service.processData(data);
return result;
} on RpcException catch (e) {
// Обрабатываем RPC-специфичные ошибки
logger.error('RPC ошибка: ${e.code} - ${e.message}');
return defaultResult;
} catch (e) {
// Обрабатываем другие ошибки
logger.error('Неожиданная ошибка: $e');
rethrow;
}
// До: Прямые вызовы методов
class Calculator {
double add(double a, double b) => a + b;
}
final calc = Calculator();
final result = calc.add(10, 5);
// После: RPC с InMemory транспортом
abstract interface class ICalculatorContract {
static const name = 'Calculator';
static const methodAdd = 'add';
}
// Настройка один раз
final (client, server) = RpcInMemoryTransport.pair();
// ... настройка эндпоинтов ...
// Использование везде
final result = await calculator.add(MathRequest(10, 5));

Когда нужно масштабироваться за пределы одного процесса:

// Разработка с InMemory
final (client, server) = RpcInMemoryTransport.pair();
// Продакшн с HTTP
final httpTransport = RpcHttpTransport(url: 'https://api.production.com');
// Тот же код сервиса работает с обоими!
final calculator = CalculatorCaller(RpcCallerEndpoint(transport: httpTransport));

InMemory транспорт - это идеальный выбор, когда вам нужна максимальная производительность внутри одного процесса, будь то тестирование, разработка или высокопроизводительные монолитные приложения. Его природа без копирования делает его непревзойдённым для сценариев, где накладные расходы на сериализацию были бы узким местом.