Какой подход вы используете для организации слоёв в больших кросс-платформенных решениях?

Организация слоёв в больших кросс-платформенных решениях на Xamarin требует строгого соблюдения принципов архитектуры и модульности. Основная цель — добиться высокой масштабируемости, поддержки тестирования, переиспользуемости кода и чёткого разделения ответственности. Подход к организации слоёв зависит от требований проекта, но в большинстве случаев применяется многоуровневая архитектура с использованием MVVM, SOLID-принципов и инверсии зависимостей.

Общий подход к архитектуре слоёв

Стандартная многослойная архитектура включает:

  1. **Presentation Layer (UI)
    **
  2. **Presentation Logic Layer (ViewModels)
    **
  3. **Application Layer (UseCases, Services)
    **
  4. **Domain Layer (Entities, Interfaces)
    **
  5. **Data Layer (Repositories, DataSources)
    **
  6. **Infrastructure Layer (Platform-specific code)
    **

Подробности по каждому слою

1. Presentation Layer (UI)

Отвечает за отображение интерфейса и взаимодействие с пользователем. В Xamarin.Forms это Pages, Views, Controls.

  • Располагается в платформенном проекте (или Forms UI-проекте)

  • Не содержит бизнес-логики

  • В идеале, только XAML + Code-behind с минимальным кодом (только инициализация)

Пример:

<Button Text="Login" Command="{Binding LoginCommand}" />

2. Presentation Logic Layer (ViewModels)

Содержит логику, управляющую состоянием UI, реализует биндинги, команды и отвечает за реакцию на действия пользователя.

  • Отнаследована от INotifyPropertyChanged или базового ViewModelBase

  • Не содержит кода платформы

  • Использует интерфейсы сервисов (например, IAuthService, INavigationService)

Пример:

public class LoginViewModel : BaseViewModel
{
public ICommand LoginCommand { get; }
public LoginViewModel(IAuthService authService)
{
LoginCommand = new Command(async () => await authService.LoginAsync(Username, Password));
}
}

3. Application Layer

Отвечает за координацию бизнес-логики: использование сценариев (use cases), агрегацию данных, управление транзакциями.

  • Пример классов: LoginUseCase, RegisterUser, GetUserProfile

  • Является посредником между ViewModel и доменной логикой

  • Может реализовывать паттерн CQRS (разделение запросов и команд)

Пример:

public class LoginUseCase
{
private readonly IAuthRepository \_authRepo;
public LoginUseCase(IAuthRepository authRepo)
{
\_authRepo = authRepo;
}
public async Task<Result> Execute(string username, string password)
{
return await \_authRepo.Login(username, password);
}
}

4. Domain Layer

Центральный слой архитектуры. Содержит бизнес-модели, интерфейсы, бизнес-правила, value objects и доменные сервисы.

  • Не зависит ни от чего

  • Можно переиспользовать в других проектах (например, в MAUI, Blazor, ASP.NET)

  • Интерфейсы для репозиториев, сервисов, провайдеров

Пример:

public class User
{
public string Username { get; set; }
public bool IsAdmin { get; set; }
public bool CanEditProfile() => IsAdmin;
}

5. Data Layer

Реализация интерфейсов репозиториев и подключение к источникам данных:

  • REST API (через HttpClient)

  • SQLite (через SQLite-net)

  • SecureStorage, Preferences, FileSystem

  • Использует DTO, Mappers и Models

Пример:

public class AuthRepository : IAuthRepository
{
private readonly IApiClient \_client;
public async Task<Result> Login(string username, string password)
{
var dto = new LoginRequest { Username = username, Password = password };
var response = await \_client.PostAsync("/login", dto);
return new Result(response.Success);
}
}

6. Infrastructure Layer (платформенный слой)

Содержит реализацию платформенных зависимостей:

  • Геолокация, камера, Bluetooth, уведомления

  • Реализует интерфейсы, объявленные в Domain или Application слое

  • Использует DependencyService или DI через контейнер

Пример:

public class AndroidCameraService : ICameraService
{
public async Task<Stream> CapturePhotoAsync() { /\* ... \*/ }
}

Использование DI-контейнера

Для инверсии зависимостей (например, с ViewModel -> UseCase -> Repository) используется контейнер:

  • Microsoft.Extensions.DependencyInjection

  • Autofac

  • DryIoc

Пример регистрации:

services.AddSingleton<IAuthService, AuthService>();
services.AddTransient<LoginViewModel>();  

В Android и iOS проекты внедряется сборка зависимостей из общей библиотеки.

Разделение по проектам

Для удобства сборки и модульности слои выносятся в отдельные сборки:

/MyApp.Solution
├── MyApp.Domain/ # Entities, interfaces
├── MyApp.Application/ # UseCases
├── MyApp.Data/ # Реализация репозиториев, API
├── MyApp.Infrastructure/ # Платформенный код
├── MyApp.Presentation/ # ViewModels
├── MyApp.MobileApp/ # Xamarin.Forms проект
├── MyApp.Android/ # Android UI
├── MyApp.iOS/ # iOS UI

Это позволяет:

  • Пересобирать только изменённые модули

  • Тестировать бизнес-логику без UI

  • Переиспользовать ядро в MAUI, Blazor, Web API

Взаимодействие между слоями

Откуда Куда Как
ViewModel → UseCase через DI Execute()
--- --- ---
UseCase → Repository через интерфейс IAuthRepository
--- --- ---
Repository → API через HttpClient или SQLite
--- --- ---
View → ViewModel через биндинги или команды
--- --- ---
Platform → Service через реализацию интерфейсов
--- --- ---

Общие принципы

  • SOLID — особенно важны SRP и DIP

  • Интерфейсы между слоями — для гибкости и мокирования

  • Изоляция UI от логики — через MVVM

  • Асинхронность — вся бизнес-логика и репозитории работают через async/await

  • Error handling — через Result<T>, Either, TryCatch, централизованную обработку ошибок

Примеры

Сценарий: Аутентификация

  • ViewModel: LoginViewModel

  • UseCase: LoginUseCase.Execute(string, string)

  • Repository: IAuthRepository.Login(...)

  • Data: AuthApiService.Post(...)

  • DTO: LoginRequest, LoginResponse

  • Infrastructure: HttpClient + JSON-сериализация

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