Coding like a sex

Post on 24-Dec-2014

10.194 views 1 download

description

Coding like a sex

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 – не блажь, а необходимость• Парное программирование для сложных

задач

Вопросы?