четверг, 28 марта 2019 г.

Структурная архитектура для MVC 5

Эта статья о реализации архитектуры для MVC 5. Код для этой статьи взят с Github.
Прежде, чем начать разработку системы, нам потребуется некоторое время, чтобы определиться с архитектурой. Причина, по которой я на этом концентрируюсь, заключается в том, что множество разработчиков либо недооценивают архитектуру приложения, либо, если вы начинающий, то большинство вещей еще не знаете.
Архитектура приложения определяет структурную модель системы. Я рекомендую вам потратить некоторое время, чтобы определиться со структурной моделью, ибо в последствие изменение архитектуры влелит вам в копеечку. В целом архитектура приложения означает разделение системы на различные компоненты, которые должны отвечать за выполнение своих функций. Эти элементы взаимодействуют между собой, чтобы обеспечить цельную работу системы.
Итак, я расскажу вам о том, как я проектирую архитектуру решения для своего проекта электронной коммерции ECommecrMVC. И для чего я это делаю.
Некоторые из вас, возможно, будут в недоумении (в основном начинающие), ведь MVC сам по себе представляет прекрасное разделение ответственности, а тогда зачем же нужна еще и архитектура? Ну да, в чем-то я с вами всеми согласен. Я сам разрабатывал свой первый проект MVC без всяких мыслей об архитектуре, но в последствии понял - код должен быть более управляемым и должен быть разделен, согласно своим обязанностям.

В решении ECommerceMVC мне требуются классы предметной области, т есть уровень домена (Domain Layer), сервисы - уровень бизнес-логики (Business Logic Layer) и пользовательский интерфейс UI - уровень представления (Presentation Layer). В ASP.NET MVC, согласно нашим требованиям, мы можем разделить приложение на Модель (Model), Представление (View) и контроллер (Controller), нам не обязательно требуется такая же архитектура, как в Visual Studio по умолчанию, которая генерирует все модели представления и контроллеры в одной корневой папке.

Для ECommerceMVC структура решения состоит из трех проектов. На начальном этапе я ограничусь темя проектами. Я организовал проекты таким образом, чтобы если потребуется переместить созданный ранее код в другой проект, я бы смог переместить его туда без дополнительных затрат.
Итак, три проекта:
  • Core Project (Class library): Библиотека классов, в структурированном виде реализующая все классы сущностей, моделей предметной области, бизнес-логику и файлы относящиеся к базам данных. One can also divide this into separate, но лично я предпочитаю содержать их в одном проекте, и перемещать их позже по необходимости. Я покажу вам это ниже в этой статье

  • Web Project (MVC Project): Это будет нашим главным проектом для запуска. Он содержит модели (для ViewModels), представления и контроллеры, а также прочие файлы, связанные с UI.

  • Test Project (Unit Test Project): Этот проект содержит модульные тесты.

Все эти проекты созданы и добавлены в пустое решение. Решение имеет следующий вид:



Добавление к имени проекта префикса такого как ECommerce позволит мне отличать один проект от другого. На рисунке выше представлено три проекта.

  • ECommerce (class library): В этом проекте я создал еще три папки. Как я уже говорил, изначально все уровни бизнес логики, уровень доступа к данным DAL, классы сущностей я создаю в данном проекте и затем по необходимости перемещаю их в другие проекты. Когда в VS мы создаем новый класс, его пространство имен автоматически включает путь к папке, где находится файл класса. Так, если я создам класс Entity.cs в папке Data, его пространство имен будет Ecommerce.Data. Структура такого вида позволяет мне переместить класс Entity.cs в проект с названием Ecommerce.Data, и никакой лишней работы делать не надо.

  • ECommerce.Web (MVC Project): Самый обычный проект MVC, который создает Visual Studio.

  • ECommerce.Test (Unit Test Project): Здесь находятся модульные тесты.

Рассмотрим каждый проект в отдельности.

1) ECommerce (Class Library):
Библиотека классов (ECommerce) имеет три папки Data, Services и Common.
В папка Data будут содержаться все классы сущностей. В папке Services будут содержаться все классы доступа к данным DAL и классы бизнес логики. В папке Common будут храниться классы общие для всех проектов.

Почему я не используею паттерн Repository?
Отвечаю: Потому что я использую Entity Framework.

Множество разработчиков использует паттерн Repository вместе с Entity Framework. Но я являюсь исключением наряду с многими другими.
Паттерн Repository является одним из самых популярных в DDD. Он скрывает реализацию логики доступа к данным. Неплохо было бы добавить в проект такую классную вещь. Но ведь Entity Framework (далее EF по тексту) делает то же самое.
EF - это ORM, избавляющая вас от написания кода доступа к данным.
Я лично не думаю, что для EF нужна обертка в виде Repository, это напоминает абстракцию абстракции. Entity Framework сам реализует паттерн Repository. Repository реализует связку паттернов паттерн "Единица работы" Unit of Work (UoW) & репозиторий, то же сделано в EF, он имеет DbContext (UoW) & DbSet (Repository), так что нет необходимости реализовывать еще один уровень поверх EF. А если мы сделаем обертку Repository поверх EF, то потеряем множество преимуществ EF. Вы можете узнать больше здесь.

Примечание: Нет ничего предосудительного в паттерне Repository, и вы можете использовать его по своему желанию.

Рассмотреим бибилиотеку классов более детально.

a) Data:

Здесь будут созданы все классы сущностей. Эта папка будет также содержать подпапки для организации сущности.
Добавим класс ProductEntity в папку Product.


В приведенном выше коде атрибуты Key (соответствует первичный ключ в таблице БД), Required (обязательное поле в форме представления),ScaffoldingColumn (отображать свойство в представлении или нет) и т.д. специфицированы, как аннотации данных в
System.ComponentModel.DataAnnotations.

Эти аннотации данных используются, как дляподдержки представления, так для и генерирования таблицы в БД.

b) Сервисы:
В этой папке будут находиться все классы бизнес-логики наряду с DbContext, и как уже говорилось, нет никакой необходимости для еще одного уровня над Entity Framework. Мне больше нравится писать сервисы, которые непосредственно возвращают значения, чем просто queryable.
Добавляя DbContext, имейте также в виду, что он является конкретной реализацией и для тестирования на DbContext потребуется создать mock-объект
Чтобы этого достичь, я внес некоторые изменения, как это описано в статье из MSDN Testing with your own doubles (EF6 onwards) для DBContext. Я установил Entity Framework 6 для проекта ECommerce через NuGet package mangager. Я буду использовать подход EF Code-First. Класс DBContext выглядит так:


ОБНОВЛЕНИЕ: Я обновил DBContext и далее создам mock-объект для его тестирования.
Проблема с вышеприведенным подходом заключается в том, что я использую фейковый класс DBContext, а это значит, что мне придется поддерживать два класса DBContext: один реальный, а второй для тестирования проекта. Чтобы этого избежать, я собираюсь создать mock-объект для DBContext из проекта ECommerce и использовать его для тестирования, не создавая отельного класса-заглушки. (как предложено egenesis on reddit)
I’m going to create generic DbContext, as I’ll be using it directly in services. Прежде, чем заняться конкретной реализацией, я для DBContext создал инерфейс IDBContext.


А вот конкретная реализация этого интерфейса:


Из этой реализации видно, что я еще и переопределил метод SaveChanges. Я буду вызывать этот метод больше в контроллере, чем в сервисах.
DBConString представляет собой строку соединения в файле app.config проекта библиотеки. Разрешив миграцию, я смогу создавать из классов сущностей таблицы БД прописанной в DBConString.

Добавим простой класс ProductServices в папку Product.
Приведенный выше код смотрится неплохо, но имеется проблема с его конкретной реализацией, хорошо бы для этих классов создать интерфейы. Чтобы создать интерфейс к классу, нажмите правой кнопкой на имени класса > Quick Action -> Extract Interface. После этого будет создан вот такой интерфейс. (Класс выше станет автоматической реализацией этого интерфейса.)
ОБНОВЛЕНИЕ: Я обновил класс ProductServices так, чтобы он использовал DbContext.
Для задействования возможностей нового DBContext, я еще немного подкорректировал класс ProductServices.
Теперь он выглядит так:


Данный подход не даст мне использовать один и тот же DbContext одновременно для сервисов и для модульных тестов.
Если какие-то сервисы имеют общие функции, то для них хорошо бы создать обобщенный абстрактный класс. По возможности, я всегда предпочитаю использовать обобщенные классы.

c) Common: В этой папке будет содержаться вспомогательный код код или общий код используемый во всех проектах.

2) ECommerce.Web(MVC Project):
Данный проект будет нашим главным проектом для запуска (Startup Project в Visual Studio).
Он содержит в корне три папки Controllers, Models и Views. As I have moved the business logic layer and data access layer to another layer, the Model folder will only contain ViewModels.
В этом проекте я через Reference Manger добафил референс на библиотеку ECommerce как показано ниже.


Для класса ProductEntity создадим контроллер и представление, используя возможности скаффолдинга. Кликните правой кнопкой на папке Controller и выберете New Scaffolded Item.


Выберите MVC 5 Controller with Views, using Entity Framework. Будут сгенерированы контроллер и Views для класса ProductEntity.



After VS does Scaffolding the solution structure will look like this for ECommerce.Web.


Для проекта ECommerceMVC, есть ряд требований требований к Панели администратора.
Если создать контроллер и представления админской панели проект ECommerce.Web наряду с Front End UI, это сделает это сделает код более неуправляемым.
Для этих целей можно создать, скажем, проект ECommerce.Admin, но мне не требуется два проекта MVC и do all the configuration stuff which I already did for ECommerce.Web.
Для таких ситуаций ASP.NET MVC предоставляет особенность, называемую области (Areas). С ее использованием я могу создать новую область, где я размещу весть код, касающийся панели администратора.
Создать область в проекте ECommerce.Web довольно простоe, надо правой кнопкой мыши кликнуть на имени проекта и выбрать Areas, после чего в нашем случае назвать область “Admin“. В проекте ECommerce.Web будет создана новая папка “Areas”.
Структура Areas папки выглядит так:

The areas creates MVC sub-project within Main MVC Project. Above diagram shows MVC structure for Area Admin, where I will write code for admin panel.
The routes for this area can be customized in AdminAreaRegistration.cs file.
To register routes for areas, I added following code to the Global.asax file in Application_Start method.
Я также добавил Connection String из app.config библиотеки классов Library project в Web.Config нашего MVC проекта.

This will be structure for ECommerce.Web для проекта ECommerceMVC.

3) ECommerce.Test (Unit Test Project):
Данный проект создается через шаблон проекта Unit Test вдиалоговом окне New Project Window.
Лично я предпочитаю в начале создавать тесты для каждого сервиса, чтобы убедиться в корректной реализации бизнес-логики.
Прежде чем начать писать тесты, я добавил к проекту следующие ссылки.
  1. Референс на библиотеку (ECommerce)
  2. Референс на проект MVC (ECommerce.Web)
  3. Entity Framework

ОБНОВЛЕНИЕ: Такой подход не рекомендуется.

Для сервисов нам потребуется реализовать фейковый DBContext, который будет использован как mock-объект для DBContext, используемого в сервисах. Чтобы этого добиться, я добавил еще два класса TestContext и TestDbSet взято отсюда. Добавим простой модульный тест для ProductService и назовем его TestProductService.cs.



ОБНОВЛЕНИЕ: Для тестирования нашего нового обобщенного DbContext сделаны следующие изменения.
А теперь изменился тест для ProductServices to mock DBContext через Moq.

Этот тест вызывает метод GetProductByCode класса ProductServices. Метод возвратит объект ProductEntity, совпадающий с тем, что мы передали в параметре метода.


Приложение ECommerceMVC будет разработано с применением этой архитектуры. Надеюсь эта статья поможет и вам.


среда, 6 марта 2019 г.

JavaScript - playing with Canvas. Cellular Automata. Клеточный автомат, играемся с Canvas.

Пример двухмерного клеточного автомата из 6 состояний (0-5) и с начальным состоянием "1" в топ-центре сетки. Каждому состоянию сопоставляется цвет (0 - чёрный, 1 - красный, 2 - синий, 3 - зеленый, 4 - желтый, 5 - белый). В пустой сетке клетки заполняются состоянием "0". Правила переходов (кодовое число) генерируются случайным образом.
Поиграться можно здесь: https://jsfiddle.net/mishau/tja1dpy8/129/

Клеточный автомат

Кодовое число:

понедельник, 28 января 2019 г.

Рефакторинг говнокода на Observer

Изначальная реализация паттерна проектирования "Наблюдатель" (Observer) для погоды

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
 
namespace lb2
{
 class Program
 {
 public static void Main()
 {
 WeatherData weatherData = new WeatherData();
 CarrentConditionDisplay carrentDisplay = new CarrentConditionDisplay(weatherData);
 //...
 weatherData.setMeasurements(80, 65, 30.4);
 weatherData.setMeasurements(82, 70, 29.2);
 weatherData.setMeasurements(78, 90, 29.2);
 
 Console.Read();
 }
 
 }
 public interface Subject
 {
 public void registerObserver(Observer 0);
 public void removeObserver(Observer 0);
 public void notifyObservers();
 }
 public interface Observer
 {
 public void update(float temp, float hum, float press);
 }
 public interface DisplayElement
 {
 public void display();
 }
 
 public class WeatherData : Subject
 {
// private string SubjectState { get; set; }
 private ArrayList observers;
 
 private float temp;
 private float hum;
 private float pressure;
 
 public WeatherData()
 {
 observers = new ArrayList ();
 }
 public void registerObserver(Observer 0)
 {
 observers.Add(0);
 }
 public  void removeObserver(Observer 0)
 {
 int i = observers.indexOF(0);
 if (i>=0) observers.remove(i);
 }
 public void notifyObservers()
 {
 foreach (Observer obs in Observers)
 Obs.opdate(temp, hum, pressure);
 }
 public  void measurementsChanges()
 {
 notifyObservers();
 }
 public  void SetMeasurements(float temp, float hum, float press);
 {
 this.temp = temp;
 this.hum = hum; 
 this.pressure = press;
 measurementsChanges();
 }
//реализовать другие методы класса
 
public class CarrentConditionDisplay:Observer, DisplayElement
 {
 private float temper;
 private float humid;
 private Subject weatherData;
 
 public CarrentConditionDisplay(Subject wData)
 {
 this.weatherData = wData;
 wData.registerObserver(this);
 }
 
 public void update(float temp, float hum, float press)
 {
 this.temper = temp;
 this.humid = hum;
 Display();
 }
 public void Display()
 {
 System.Console.WriteLine("Текущие условия" + temper + "С и " + humid + "% влажности");
 }
 }
}