This commit is contained in:
2026-04-21 12:51:23 +03:00
parent 526353d679
commit 815283a776
5 changed files with 2436 additions and 0 deletions

607
ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,607 @@
# 🏗️ Архитектура YandexMusic
Полное описание архитектуры, компонентов и взаимодействия между слоями решения YandexMusic.
## 📋 Содержание
- [Обзор архитектуры](#обзор-архитектуры)
- [Структура слоёв](#структура-слоёв)
- [Основные компоненты](#основные-компоненты)
- [Потоки данных](#потоки-данных)
- [Паттерны проектирования](#паттерны-проектирования)
- [Модульная структура](#модульная-структура)
- [Расширяемость](#расширяемость)
## 🔍 Обзор архитектуры
### Многослойная архитектура
Проект построен на классической трёхслойной архитектуре с элементами DDD:
```
┌────────────────────────────────────────┐
│ CLI Layer (YaMusicCli) │ ← Точка входа
├────────────────────────────────────────┤
│ Client Layer (YandexMusic) │ ← Удобный интерфейс
├────────────────────────────────────────┤
│ API Layer (YandexMusic.API) │ ← Логика API
├────────────────────────────────────────┤
│ Data Layer (Models) │ ← Данные и сущности
├────────────────────────────────────────┤
│ Infrastructure Layer │ ← HTTP, Serialization
└────────────────────────────────────────┘
```
### Ключевые принципы
- **Separation of Concerns** - каждый слой отвечает за одно
- **Dependency Injection** - слабая связанность через интерфейсы
- **Single Responsibility** - каждый класс решает одну задачу
- **Open/Closed Principle** - открыто для расширения, закрыто для модификации
- **Asynchronous First** - всё асинхронное по умолчанию
## 📦 Структура слоёв
### 1. CLI Layer (YaMusicCli)
**Назначение:** Командная строка интерфейс для пользователей.
```
YaMusicCli/
├── Program.cs # Точка входа, парсинг аргументов
└── Commands/ # Команды для CLI
├── SearchCommand
├── PlaylistCommand
└── TrackCommand
```
**Ответственность:**
- Парсинг команд пользователя
- Вывод результатов в консоль
- Обработка пользовательского ввода
- Форматирование данных для вывода
**Зависимости:** YandexMusic (Client Layer)
### 2. Client Layer (YandexMusic)
**Назначение:** Удобный оборачиватель (wrapper) для работы с API.
```
YandexMusic/
└── YandexMusicClient.cs
├── Initialization (конструктор)
├── Authorization (авторизация)
├── Properties (доступ к API и хранилищу)
└── Cleanup (dispose)
```
**Основной класс:**
```csharp
public class YandexMusicClient : IDisposable
{
// Инициализация
public YandexMusicClient(
CookieContainer? cookieContainer = null,
IWebProxy? proxy = null,
TimeSpan? timeout = null,
string? userAgent = null)
// Авторизация
public async Task AuthorizeAsync(string login, string password)
public async Task AuthorizeByTokenAsync(string token)
// Свойства доступа
public YandexMusicApi Api { get; } // API Яндекс Музыки
public AuthStorage AuthStorage { get; } // Хранилище данных
public HttpClient HttpClient { get; } // HttpClient
public YAccount Account { get; } // Информация об аккаунте
public bool IsAuthorized { get; } // Статус авторизации
public YnisonPlayer? Ynison { get; } // WebSocket плеер
}
```
**Ответственность:**
- Упрощение работы с API
- Управление HttpClient и cookies
- Обработка авторизации
- Инициализация компонентов
**Зависимости:** YandexMusic.API (API Layer)
### 3. API Layer (YandexMusic.API)
**Назначение:** Низкоуровневой доступ к API Яндекс Музыки.
```
YandexMusic.API/
├── YandexMusicApi.cs # Главный класс - фасад
├── API/ # API классы по функциям
│ ├── YAlbumAPI
│ ├── YArtistAPI
│ ├── YTrackAPI
│ ├── YPlaylistAPI
│ ├── YRadioAPI
│ ├── YSearchAPI
│ └── ... (остальные API)
├── Requests/ # Построители запросов
│ ├── Common/
│ ├── Album/
│ ├── Artist/
│ └── ... (по категориям)
├── Models/ # Модели данных
│ ├── Common/
│ ├── Album/
│ ├── Track/
│ └── ... (по типам)
└── Common/ # Вспомогательные компоненты
├── AuthStorage
├── Providers/
├── Encryptor
└── Ynison/
```
**Главный класс:**
```csharp
public class YandexMusicApi
{
// API Методы
public YAlbumAPI Album { get; }
public YArtistAPI Artist { get; }
public YTrackAPI Track { get; }
public YPlaylistAPI Playlist { get; }
public YRadioAPI Radio { get; }
public YSearchAPI Search { get; }
// ... и так далее для всех веток API
}
```
**API Классы:**
Каждый API класс наследуется от `YCommonAPI`:
```csharp
public abstract class YCommonAPI
{
protected readonly YandexMusicApi api;
protected YCommonAPI(YandexMusicApi yandex) => api = yandex;
}
// Пример конкретного API
public class YTrackAPI : YCommonAPI
{
public YTrackAPI(YandexMusicApi yandex) : base(yandex) { }
public async Task<YTrack?> GetTrackAsync(string trackId) { }
public async Task<IEnumerable<YTrack>> GetTracksAsync(IEnumerable<string> ids) { }
// ... остальные методы
}
```
**Ответственность:**
- Построение HTTP запросов
- Вызов провайдера запросов
- Десериализация ответов
- Обработка ошибок API
### 4. Data Layer (Models)
**Назначение:** Модели данных, отражающие структуру API Яндекс Музыки.
```
Models/
├── Common/ # Общие модели
│ ├── YBaseModel # Базовая модель с контекстом
│ ├── YResponse<T> # Ответ от API
│ ├── YError # Ошибка
│ └── ... (остальные)
├── Album/ # Модели альбомов
│ ├── YAlbum
│ └── YAlbumMenuItem
├── Track/ # Модели треков
│ ├── YTrack
│ ├── YTrackSupplement
│ └── YTrackSimilar
├── Playlist/ # Модели плейлистов
│ ├── YPlaylist
│ ├── YPlaylistChange
│ └── YPlaylistMadeFor
└── ... (остальные модели)
```
**Базовая модель:**
```csharp
public abstract class YBaseModel
{
[JsonIgnore]
public YExecutionContext? Context { get; set; }
}
// Пример модели
public class YTrack : YBaseModel
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("title")]
public string Title { get; set; }
[JsonPropertyName("duration_ms")]
public int DurationMs { get; set; }
// ... остальные свойства
}
```
**Ответственность:**
- Представление данных API
- System.Text.Json сериализация/десериализация
- Хранение контекста выполнения (опционально)
### 5. Infrastructure Layer
**Назначение:** Низкоуровневая инфраструктура для работы с HTTP и сериализацией.
```
Common/
├── Providers/ # Провайдеры запросов
│ ├── IRequestProvider # Интерфейс
│ ├── DefaultRequestProvider
│ ├── CommonRequestProvider
│ └── MockRequestProvider
├── AuthStorage # Управление авторизацией
├── Encryptor # Шифрование данных
├── DataDownloader # Загрузка файлов
└── Ynison/ # WebSocket
├── YnisonPlayer
└── YnisonWebSocket
```
**IRequestProvider:**
```csharp
public interface IRequestProvider
{
// Выполняет HTTP запрос
Task<HttpResponseMessage> GetWebResponseAsync(
HttpRequestMessage message,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead);
// Преобразует ответ в модель
Task<T> GetDataFromResponseAsync<T>(
YandexMusicApi api,
HttpResponseMessage response);
}
```
## 🔄 Потоки данных
### Типичный поток запроса
```
1. User Action
2. YandexMusicClient Method
3. YandexMusicApi.SomeAPI.SomeMethodAsync()
4. YRequestBuilder<T> builds HttpRequestMessage
5. IRequestProvider.GetWebResponseAsync()
6. HttpClient sends request
7. Server responds with HttpResponseMessage
8. IRequestProvider.GetDataFromResponseAsync<T>()
9. System.Text.Json deserializes to Model<T>
10. Model<T> returned to caller
```
### Пример: Получение трека
```csharp
// 1. Пользователь вызывает метод
var track = await client.Api.Track.GetTrackAsync("trackId123");
// 2. YTrackAPI.GetTrackAsync() создаёт запрос
var builder = new YGetTracksBuilder(api);
var message = builder.Build("trackId123");
// 3. Запрос отправляется через провайдер
var response = await authStorage.Provider.GetWebResponseAsync(message);
// 4. Ответ преобразуется в модель
var track = await provider.GetDataFromResponseAsync<YTrack>(api, response);
// 5. Модель возвращается пользователю
return track;
```
## 🎯 Паттерны проектирования
### 1. Facade Pattern (YandexMusicApi)
```csharp
// Фасад предоставляет простой интерфейс к сложной подсистеме
public class YandexMusicApi
{
public YAlbumAPI Album { get; } // Скрывает сложность
public YArtistAPI Artist { get; } // инициализации
public YTrackAPI Track { get; } // каждого компонента
}
```
### 2. Builder Pattern (Request Builders)
```csharp
// Построитель для конструирования запросов
public class YSearchBuilder : YRequestBuilder<YSearch>
{
private string _query;
private int _page = 0;
public YSearchBuilder Query(string q) { _query = q; return this; }
public YSearchBuilder Page(int p) { _page = p; return this; }
public override HttpRequestMessage Build() { /* ... */ }
}
// Использование
var search = new YSearchBuilder()
.Query("Beatles")
.Page(1)
.Build();
```
### 3. Strategy Pattern (IRequestProvider)
```csharp
// Интерфейс позволяет выбрать стратегию обработки запросов
public interface IRequestProvider
{
Task<HttpResponseMessage> GetWebResponseAsync(HttpRequestMessage message);
}
// Разные реализации для разных сценариев
public class DefaultRequestProvider : IRequestProvider { }
public class MockRequestProvider : IRequestProvider { }
public class CustomRequestProvider : IRequestProvider { }
```
### 4. Dependency Injection
```csharp
// Зависимости передаются через конструктор
public class YTrackAPI : YCommonAPI
{
protected readonly YandexMusicApi api; // Injected
public YTrackAPI(YandexMusicApi yandex) => api = yandex;
}
```
### 5. Template Method Pattern
```csharp
// Базовый класс определяет структуру
public abstract class YCommonAPI
{
protected readonly YandexMusicApi api;
protected YCommonAPI(YandexMusicApi yandex) => api = yandex;
}
// Подклассы реализуют специфичные методы
public class YAlbumAPI : YCommonAPI
{
// Реализация методов для альбомов
}
```
## 🧩 Модульная структура
### Логическое разделение на модули
```
Core Module (YandexMusic.API)
├── Authentication Module
│ ├── AuthStorage
│ └── Authorization methods
├── Request Module
│ ├── IRequestProvider
│ ├── Request builders
│ └── HttpContext
├── Model Module
│ └── All data models
└── API Module
├── YAlbumAPI
├── YTrackAPI
├── YPlaylistAPI
└── ... (each API endpoint)
Client Module (YandexMusic)
├── YandexMusicClient
└── Client configuration
CLI Module (YaMusicCli)
├── Program.cs
└── CLI commands
```
### Зависимости между модулями
```
CLI Module
↓ depends on
Client Module
↓ depends on
Core Module
├── Authentication Module
├── Request Module
├── Model Module
└── API Module
```
## 🔌 Расширяемость
### Как добавить новый API метод
```csharp
// 1. Создайте builder в Requests/YourCategory/
public class YGetYourResourceBuilder : YRequestBuilder<YYourResource>
{
public override HttpRequestMessage Build()
{
// Реализуйте построение запроса
}
}
// 2. Добавьте метод в соответствующий API класс
public class YYourResourceAPI : YCommonAPI
{
public async Task<YYourResource?> GetYourResourceAsync(string id)
{
var builder = new YGetYourResourceBuilder(api);
var request = builder.Build();
return await new YRequest<YYourResource?>(request, api, api.AuthStorage).GetResponseAsync();
}
}
// 3. Используйте новый метод
var resource = await api.YourResource.GetYourResourceAsync("id123");
```
### Как создать custom RequestProvider
```csharp
public class MyCustomRequestProvider : IRequestProvider
{
private readonly AuthStorage _storage;
public MyCustomRequestProvider(AuthStorage storage)
{
_storage = storage;
}
public async Task<HttpResponseMessage> GetWebResponseAsync(
HttpRequestMessage message,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead)
{
// Ваша логика обработки запроса
using var handler = new HttpClientHandler();
using var client = new HttpClient(handler);
return await client.SendAsync(message, completionOption);
}
public async Task<T> GetDataFromResponseAsync<T>(
YandexMusicApi api,
HttpResponseMessage response)
{
// Ваша логика десериализации
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<T>(content) ?? default!;
}
}
// Использование
var provider = new MyCustomRequestProvider(storage);
var storage = new AuthStorage(provider);
```
### Как создать extension method
```csharp
public static class YPlaylistExtensions
{
public static async Task<IEnumerable<YTrack>> GetAllTracksAsync(
this YPlaylist playlist,
YandexMusicApi api)
{
// Расширение функциональности существующего класса
var allTracks = new List<YTrack>();
var currentPage = 0;
while (true)
{
var page = await api.Playlist.GetPlaylistAsync(
playlist.Uid,
page: currentPage);
if (page?.Tracks?.Count == 0)
break;
allTracks.AddRange(page.Tracks ?? []);
currentPage++;
}
return allTracks;
}
}
```
## 📊 Диаграмма взаимодействия компонентов
```
┌──────────────────┐
│ CLI Layer │
│ (YaMusicCli) │
└────────┬─────────┘
┌──────────────────────────┐
│ Client Layer │
│ (YandexMusicClient) │
└────────┬─────────────────┘
┌────────────────────────────────────┐
│ API Layer │
│ ┌─────────────────────────────┐ │
│ │ YandexMusicApi (Facade) │ │
│ └──┬──────────────────────────┘ │
│ │ │
│ ┌──┴─────────┬─────────────────┐│
│ ↓ ↓ ↓│
│ YTrackAPI YPlaylistAPI YSearchAPI │
│ │ │ │ │
│ └──────┬─────┴────────┬────────┘ │
└─────────┼──────────────┼───────────┘
│ │
↓ ↓
┌──────────────────────────┐
│ IRequestProvider │
│ (Strategy Pattern) │
└──────┬───────────────────┘
┌──┴───┬──────────┐
↓ ↓ ↓
Default Common Mock
Provider Provider Provider
│ │ │
└──────┼────────┘
┌──────────────────┐
│ HttpClient │
└──────┬───────────┘
┌──────────────────┐
│ Network Layer │
└──────────────────┘
```
## 🔐 Безопасность архитектуры
1. **Инкапсуляция** - приватные поля и контролируемый доступ
2. **Валидация** - все входные параметры валидируются
3. **Null-safety** - полная поддержка nullable reference types
4. **Async-safe** - асинхронные операции без blocking
5. **Encryption** - встроенное шифрование для чувствительных данных