Coding like a sex
-
Upload
max-arshinov -
Category
Presentations & Public Speaking
-
view
10.194 -
download
1
description
Transcript of Coding like a sex
Максим Аршинов
IT–гуру, μISVОбрел просветление в разработке ПО
Программирование – это как секс
• DDD• CQRS• Event Sourcing• Эволюционный рефакторинг• Горизонтальное масштабирование и облачная архитектура• Эффективное взаимодействие между командами
Один раз ошибешься, а потом поддерживать всю жизнь
Программирование – это как секс
А проектирование – это как петтинг
Распределенные и/или нагруженные бизнес-приложения
• Система продажи билетов• Трейдинговая площадка• Баннерная сеть для порно-сайтов
Проблемы
• Специфическая терминология• Сложная предметная область, не
очевидные бизнес-правила• Бизнес-логика может часто меняться• Высокая и/или неравномерная нагрузка
Цели разработчика
• Сохранять темпы разработки• Повысить bus factor• Обеспечить горизонтальное
масштабирование системы
«Архитектура»
• Что это такое?• Чем отличается от «инфраструктуры»?
Всему свое время
Трёхуровневая архитектура
UI
Business Rules
Data Access Layer
Metal Gear
• S – Single responsibility principle• O – Open/closed principle• L – Liskov substitution principle• I – Interface segregation principle• D – Dependency inversion principle
Как структурировать бизнес-логику?
Как структурировать бизнес-логику?
• DDD (Domain Driven Design)• Инфраструктура – не домен!• Persistence ignorance• Aggregation Roots• Anemic Domain Model Rich Models• Active Record Unit of Work• Controller Service Layer• using(var uow = new UoW()), Singleton, Registry
IOC
Единый язык (Ubiquitous language)
ticket.State = TicketState.Sold; ticketRepository.Update(ticket); var ticket = cashdesk.Sale (seat);
DDD vs OOP
• Гвоздь суше воды• DDD про отделение мух от котлет домена от
инфраструктуры• DDD про взаимодействие с бизнесом
Entity
• Есть Id• У вас есть, у гвоздя – нет• Наличие или отсутствие Id может зависеть
от контекста
CRUD
Что не так с Repository?
public interface IRepository<T>{
T GetById(int id);IEnumerable<T> GetAll();
bool Add(T entity);bool Remove(T entity);
}
Что не так с Repository?class AccountRepository : IRepository<Account>{
public Account GetByName(string name);public Account GetByEmail(string email);
public Account GetByAge(int age);
// ...
public Account GetByAreYouFuckingKiddingMe(SomeCriteria c);}
Что не так с Repository?
• 100500 методов, в т.ч. в интерфейсе IAccountRepository
• Не удобно тестировать• Многа-букаф, чтобы написать новый метод• Приходится прокидывать сигнатуры в
сервисный слой• Рефакторинг затруднен
Ок, я понял
public interface IRepository<T>{
T GetById(int id);//во имя луныIQueryable<T> GetAll();
bool Add(T entity);bool Remove(T entity);
}
Что не так с Repository?
repo.GetAll().Where(a => a.IsDeleted = false);
repo.GetAll().Where(a => a.IsDeleted = false &&
a.Balance > 0);
repo.GetAll().Where(a => a.CreationDate < getCurrentDate());
Specification Pattern
public interface ISpecification<T>{
bool IsSatisfiedBy(T candidate);}
Expression Specification
public interface ISpecification<T>{
Expression<Func<T, bool>> IsSatisfiedBy();}
Стало лучшеIEnumerable<T> GetBySpecifications(
IEnumerable<ISpecification<T>> specifications,params IFetchStrategy<T>[] fetchStrategies);
FilterPolicy (Value Object)public class FilterPolicy<T>{
public IEnumerable<ISpecification<T>> Specifications { get; private set; }public IEnumerable<IFetchStrategy<T>> FetchStrategies { get; private set; }
public FilterPolicy( IEnumerable<ISpecification<T>> specifications, IEnumerable<IFetchStrategy<T>> fetchStrategies) { Specifications = specifications; FetchStrategies = fetchStrategies; }}
Более подробно о Value Object
• Не Entity• Immutable• Value - не Reference
Защитное программирование на страже инварианта
public class FilterPolicy(IEnumerable<ISpecification<T>> specifications,IEnumerable<IFetchStrategy<T>> fetchStrategies)
{if(specifications == null) throw new ArgumentException(
"specifications can\'t be null", "specifications");if(fetchStrategies == null) throw new ArgumentException(
"fetchStrategies can\'t be null", "fetchStrategies");
Specifications = specifications;FetchStrategies = fetchStrategies;
}
Composite
• OrSpecification• AndSpecification• ActiveAccountSpecification …
Compositepublic class OrSpecification<T> : ISpecification<T>{
private readonly ISpecification<T>[] _specs;
public OrSpecification(params ISpecification<T>[] specs){
if(specs.Length == 0) throw new ArgumentException("specs length must be > 0", "specs");
_specs = specs;}
public Expression<Func<T, bool>> IsSatisfiedBy(){
throw new NotImplementedException();}
}
Декларативный стиль > императивного
• DSL• Функциональное программирование,
монады• Мета-программирование и AOP, Code
Contracts• Кодогенерация• Динамическая компиляция
Монада Maybestring postCode;if (person != null){
if (HasMedicalRecord(person) && person.Address != null){
CheckAddress(person.Address);if (person.Address.PostCode != null)postCode = person.Address.PostCode.ToString();elsepostCode = "UNKNOWN";
}}
public static TResult With<TInput, TResult>(this TInput o, Func<TInput, TResult> evaluator)where TResult : class where TInput : class
{if (o == null) return null;return evaluator(o);
}
string postCode = this.With(x => person) .With(x => x.Address) .With(x => x.PostCode)
.Return(x => "UNKNOWN");
Conventions > configuration
[DisplayName("Категории продуктов")][DataContract]public class ProductCategory : EntityBase{
[Display(Name = "Название")][Column(TypeName = "VARCHAR"), StringLength(255), Required][DataMember(IsRequired = true)]public string Name { get; set; }
public override string ToString(){
return Name;}
}
Иногда лучше по-быстрому, чем SRP, если знаешь как будешь рефакторить
public class ProductCategoryController : AdminControllerBase<ProductCategory>{
public ProductCategoryController(DbContext dbContext) : base(dbContext){}
}
Например, INotifyPropertyChanged
Дай разработчику дефолтное поведение и он будет штамповать формочки весь день. Научи как переопределить
его…
• Во FreeBSD ты можешь настроить все, и ты %$@ть будешь настраивать все
• ASP.NET MVC• WCF
Разделяй и властвуй
• Module aka Package• Onion - архитектура• SOA
Bounded Context
• Anti-Corruption Layer• Контекстов может быть много
Anti-Corruption Layer. How To?class Messy{
String concat(String param, String str) { /* ... */ }bool contains(String param, String s) { /* ... */ }bool isEmpty(String param) { /* ... */ }bool matches(String param, String regex) { /* ... */ }bool startsWith(String param, String prefix) { /* ... */ }
}
class Reasonable // anti-corruption layer{
String param;Messy messy = new Messy();
Reasonable(String param){
this.param = param; }
String concat(String str) { return messy.concat(param, str); }bool contains(String s) { return messy.contains(param, s); }bool isEmpty() { return messy.isEmpty(param); }bool matches(String regex) { return messy.matches(param, regex); }bool startsWith(String prefix) { return messy.startsWith(param, prefix); }
}
Разгрузочный слайд
Shared DB
Реляционная база данных – узкое место
Оптимизация БД
• Убираем ORM для лучшей оптимизации• Оптимизируем индексы• Убираем весь код выборки в хранимые
процедуры• Строим/оптимизируем индексы• Денормализуем данные (когда ничего не
помогает)
Стратегии денормализации
• Убрать JOIN’ы, добавить денормализованные колонки
• Создаем отдельные таблицы/view• Хранилище с «плоскими» данными
Теорема CAP
• Consistency (согласованность данных)• Availability (доступность)• Partition tolerance (устойчивость к
разделению)
2 из 3
• Consistency + Availability (традиционные СУБД)• Consistency + Partition tolerance
(пессимистические блокировки)• Availability + Partition tolerance (NOSQL-решения)
CQS
• Command–query separation• Commands – update state (C, U, D)• Queries – fetch results (R)
CQS – зачем?
• Отсутствие сайд-эффектов• Поддержка декларативного стиля (WCF)• Горизонтальное масштабирование
Query Object как альтернатива Repository
• Абстракция от ORM (но велосипед)• Не надо городить кучу IEntityRepository• Просто тестировать• SRP
QueryObject и QueryBuilderpublic interface IQueryFor<out T>{
T With(ISpecification<T> specification)T ById(int id);IEnumerable<T> All();
}
public interface IQueryBuilder{
IQueryFor<TResult> For<TResult>();}
var account = queryBuilder.For<Account>().With(new LoginSpecification("Вася")).All();
Command Pattern
public interface ICommand{
void Execute();}
Не все CQS by design
• Например Стек• Помним про Bounded Context
Синхронизация хранилищ
• Синхронно (C,U,D)
Синхронизация хранилищ
• Асинхронно
Команды и шина
• bus.Send(command);• Диспетчеризация• Логирование
Минусы
• Eventually consistent• Архитектура гораздо сложнее, чем Shared
DB• Обработка ошибок• UX Issues
Горизонтальное масштабирование записи данных
• Очередь сообщений (Producer-Consumer pattern)
• RabbitMQ• IronMQ• NServiceBus
Разгрузочный слайд
Event Sourcing – вспомнить все
Проблемы
• Рефакторинг• Сложность системы, Bus factor• Сторонние сервисы• Выбор Aggregation Root• Tool Support
Взаимодействие между командами, границы применимости
• И снова Bounded Context• UI-команда – клиент для Backend-команды• Сначала интерфейс, потом реализация• CQRS и ES могут жить в подсистеме• Code Review – не блажь, а необходимость• Парное программирование для сложных
задач
Вопросы?