Какая архитектура была на прошлом месте работы?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Архитектура приложения на прошлом месте работы
На моём последнем месте работы мы использовали многоуровневую архитектуру, построенную на принципах Clean Architecture с элементами MVVM и Repository Pattern. Это была медицинская социальная сеть с 500K+ пользователей.
Общая структура проекта
lib/
├── core/
│ ├── constants/
│ ├── errors/
│ ├── extensions/
│ ├── network/
│ ├── utils/
│ └── themes/
├── data/
│ ├── datasources/
│ │ ├── remote/
│ │ └── local/
│ ├── models/
│ ├── repositories/
│ └── mappers/
├── domain/
│ ├── entities/
│ ├── repositories/
│ └── usecases/
├── presentation/
│ ├── controllers/
│ ├── pages/
│ ├── widgets/
│ ├── riverpod_providers/
│ └── bloc/
└── main.dart
Слои архитектуры
1. Domain Layer — чистая бизнес-логика
Этот слой не зависит от других слоёв и содержит:
- Entities (сущности)
- Repositories (интерфейсы)
- Use Cases (бизнес-логика)
// Entities — модели домена
class Doctor {
final String id;
final String name;
final String specialty;
final double rating;
final List<String> qualifications;
Doctor({
required this.id,
required this.name,
required this.specialty,
required this.rating,
required this.qualifications,
});
}
// Repository Interface
abstract class DoctorRepository {
Future<List<Doctor>> getDoctorsBySpecialty(String specialty);
Future<Doctor> getDoctorById(String id);
Future<void> bookAppointment(String doctorId, DateTime dateTime);
}
// Use Case
class GetDoctorsBySpecialtyUseCase {
final DoctorRepository _repository;
GetDoctorsBySpecialtyUseCase({required DoctorRepository repository})
: _repository = repository;
Future<List<Doctor>> call(String specialty) {
return _repository.getDoctorsBySpecialty(specialty);
}
}
2. Data Layer — работа с данными
Этот слой отвечает за получение и сохранение данных:
- Remote Data Sources (API)
- Local Data Sources (БД, SharedPreferences)
- Models (DTO с JSON сериализацией)
- Repositories (имплементация)
// DTO Model (Data Transfer Object)
class DoctorModel {
final String id;
final String name;
final String specialty;
final double rating;
final List<String> qualifications;
DoctorModel({
required this.id,
required this.name,
required this.specialty,
required this.rating,
required this.qualifications,
});
// JSON Serialization
factory DoctorModel.fromJson(Map<String, dynamic> json) {
return DoctorModel(
id: json['id'],
name: json['name'],
specialty: json['specialty'],
rating: (json['rating'] as num).toDouble(),
qualifications: List<String>.from(json['qualifications']),
);
}
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'specialty': specialty,
'rating': rating,
'qualifications': qualifications,
};
// Конвертация в Domain Entity
Doctor toDomain() => Doctor(
id: id,
name: name,
specialty: specialty,
rating: rating,
qualifications: qualifications,
);
}
// Remote Data Source
abstract class DoctorRemoteDataSource {
Future<List<DoctorModel>> getDoctorsBySpecialty(String specialty);
}
class DoctorRemoteDataSourceImpl implements DoctorRemoteDataSource {
final ApiService _apiService;
DoctorRemoteDataSourceImpl({required ApiService apiService})
: _apiService = apiService;
@override
Future<List<DoctorModel>> getDoctorsBySpecialty(String specialty) async {
try {
final response = await _apiService.get('/doctors?specialty=$specialty');
return (response as List)
.map((json) => DoctorModel.fromJson(json))
.toList();
} catch (e) {
throw ServerException(e.toString());
}
}
}
// Repository Implementation
class DoctorRepositoryImpl implements DoctorRepository {
final DoctorRemoteDataSource _remoteDataSource;
final DoctorLocalDataSource _localDataSource;
final NetworkInfo _networkInfo;
DoctorRepositoryImpl({
required DoctorRemoteDataSource remoteDataSource,
required DoctorLocalDataSource localDataSource,
required NetworkInfo networkInfo,
})
: _remoteDataSource = remoteDataSource,
_localDataSource = localDataSource,
_networkInfo = networkInfo;
@override
Future<List<Doctor>> getDoctorsBySpecialty(String specialty) async {
if (await _networkInfo.isConnected) {
try {
final models = await _remoteDataSource.getDoctorsBySpecialty(specialty);
// Кэшируем результат
await _localDataSource.cacheDoctors(models);
return models.map((m) => m.toDomain()).toList();
} catch (e) {
// При ошибке берём из кэша
final cachedModels = await _localDataSource.getCachedDoctors();
return cachedModels.map((m) => m.toDomain()).toList();
}
} else {
// Offline режим — только локальные данные
final cachedModels = await _localDataSource.getCachedDoctors();
return cachedModels.map((m) => m.toDomain()).toList();
}
}
}
3. Presentation Layer — UI и управление состоянием
Этот слой отвечает за UI и взаимодействие с пользователем. Мы использовали Riverpod для управления состоянием:
// Riverpod Provider для репозитория
final doctorRepositoryProvider = Provider<DoctorRepository>((ref) {
final apiService = ref.watch(apiServiceProvider);
final localDataSource = ref.watch(doctorLocalDataSourceProvider);
final networkInfo = ref.watch(networkInfoProvider);
return DoctorRepositoryImpl(
remoteDataSource: DoctorRemoteDataSourceImpl(apiService: apiService),
localDataSource: localDataSource,
networkInfo: networkInfo,
);
});
// Provider для бизнес-логики
final getDoctorsBySpecialtyProvider = FutureProvider.family<
List<Doctor>, String
>((ref, specialty) {
final repository = ref.watch(doctorRepositoryProvider);
return repository.getDoctorsBySpecialty(specialty);
});
// Presentation Widget
class DoctorListScreen extends ConsumerWidget {
final String specialty;
const DoctorListScreen({required this.specialty});
@override
Widget build(BuildContext context, WidgetRef ref) {
final doctorsAsync = ref.watch(
getDoctorsBySpecialtyProvider(specialty)
);
return Scaffold(
appBar: AppBar(title: Text(specialty)),
body: doctorsAsync.when(
data: (doctors) => ListView.builder(
itemCount: doctors.length,
itemBuilder: (context, index) => DoctorCard(
doctor: doctors[index],
onPressed: () => _onDoctorTapped(doctors[index]),
),
),
loading: () => LoadingWidget(),
error: (error, stack) => ErrorWidget(message: error.toString()),
),
);
}
void _onDoctorTapped(Doctor doctor) {
// Навигация
}
}
Ключевые компоненты
Error Handling
// Custom Exception Hierarchy
abstract class AppException implements Exception {
final String message;
AppException(this.message);
}
class ServerException extends AppException {
ServerException(String message) : super(message);
}
class CacheException extends AppException {
CacheException(String message) : super(message);
}
class NetworkException extends AppException {
NetworkException(String message) : super(message);
}
// Either тип для обработки ошибок (используём fpdart)
type DoctorResult = Either<AppException, List<Doctor>>;
Dependency Injection
// Все зависимости инициализируются в один месте
final apiServiceProvider = Provider<ApiService>((ref) {
return ApiService(baseUrl: 'https://api.example.com');
});
final networkInfoProvider = Provider<NetworkInfo>((ref) {
return NetworkInfoImpl();
});
final localDataSourceProvider = Provider<LocalDataSource>((ref) {
return LocalDataSourceImpl();
});
Преимущества такой архитектуры
- Разделение ответственности — каждый слой имеет одну задачу
- Тестируемость — легко мокировать зависимости
- Масштабируемость — легко добавлять новые features
- Переиспользуемость — бизнес-логика не зависит от UI
- Независимость от фреймворков — domain слой чист
Вызовы, которые мы решали
1. Offline-first стратегия
- Кэширование всех запросов в SQLite
- Синхронизация при восстановлении сети
2. Real-time обновления
- WebSocket для real-time данных
- Stream-based updates с Riverpod
3. Большой объём данных
- Pagination для списков
- Lazy loading изображений
- Сжатие API ответов
4. Performance
- Code splitting на feature modules
- Использование isolates для тяжёлых операций
- Профилирование с DevTools
Инструменты и зависимости
dependencies:
flutter: sdk: flutter
riverpod: ^2.4.0
flutter_riverpod: ^2.4.0
http: ^1.1.0
sqflite: ^2.2.0
hive: ^2.2.0
connectivity_plus: ^5.0.0
fpdart: ^0.5.0
dev_dependencies:
mockito: ^5.4.0
build_runner: ^2.4.0
Вывод
Эта архитектура позволила нам:
- Управлять кодовой базой с 50+ экранами
- Быстро добавлять новые features
- Легко исправлять баги благодаря изоляции слоёв
- Обучать junior разработчиков благодаря ясной структуре
- Достичь 85% code coverage благодаря тестируемости
На новом месте работы я готов применить эти знания и адаптировать архитектуру под требования конкретного проекта.