14. OData
RESTful
APIs
14
OData helps you focus on your business logic!
You don’t worry about:
- the approaches to define request and response headers,
- status codes,
- HTTP methods,
- URL conventions,
- media types,
- payload formats,
- query options etc.
22. Vacancy
page
22
Get all info about a certain
vacancy and candidates
included in this vacancy
Mark candidates with a “star”
to make them shortlisted
24. Create a
new
candidate
24
Add a new candidate by
entering his data yourself or let
Hunter fetch information about
the candidate from his LinkedIn
profile
29. Create a
new
candidate
29
Use notes of certain types to
comment on candidate’s skills
Use “thumb” buttons to
highlight positive or negative
notes with color
30. Create and view Feedbacks & Notes history with the Feedbacks menu that is located in Candidates
Card
30
Feedbacks &
Notes
Період навчання - більше 3х місяців
Почали працювати над проектом 21 липня 2015
Презентація проекту - 4 вересня 2015 року
Працювали по скрам підходу з щоденними мітінгами всього біля 40 днів )))
Project tracking з допомогою Trello.
Всього більше 200 issues.
БД біля 20 таблиць.
Всього більше 6500 стрічок коду.
Для спільної розробки використовували GitHub - де зробили більше 900 комітів )
Кілька слів про user story.
User story overview
Групи користувачів зі своїми правами
Керування вакансіями, їх швидкий пошук
Керування кандидатами, їх швидкий пошук
Повязування/керування кандидата і вакансії - історія змін, коментарі, нотифікції, етапи роботи і тд
Після отримання і аналізу User story почали роботу у таких основних напрямах:
1. макети (робота велись у цьому напрямі докінця проекту з метою покращення usability)
2. проектування БД (далі поговоримо про підходи, які використовували)
3. і сама архітектура прикладення - яка розгорнулася у написання коду прикладення як на стороні сервера, так і клієнта
Дизайн
Як середовище використовували ресурс ninjamock.com
Всього створено було біля 20 моків ), що накривали основні сторінки прикладення.
Як СУБД використовували SQL Server.
Визанчено основні сутності, їх атрибути. З використанням SQL було створено таблиці з необхідними полями, визначено типи звязків і класи належності антибутів сутностей.
Для доступу до даних з прикладення використовується технологія Entity Framework.
Як відомо є 3 шаблони для роботи з даними в Entity Framework:
Database First,
Model First,
Code First.
Ми використали шаблон Code First.
Для поетапного розширення бази данних використовувалися міграції.
Міграції добавлялися через NuGet Package Manager Console.
Переваги:
Однотипність виконання кожної зміни
Строгий порядок внесення змін
(!!!) Можливість відкотити зміни
Головні переваги використання міграцій - це версіонність бази данних.
Це дає можливість при кожній зміні структури моделей данних автоматично переносити зміни на саму базу данних, та при необхідності відкочуватись до потрібної версії.
http://www.entityframeworktutorial.net/code-first/code-based-migration-in-code-first.aspx
Організація роботи з данними була реалізована через паттерн Repository.
Головна перевага репозиторіїв - це абстрактний механізм зберігання колекцій сутностей данних.
Інтерфейс IRepository дає можливість не залежати від технології зберігання данних.
Таким чином, прикладення “знає” тільки абстрактне поняття MemberRepository та його використання може бути відокремлено від фактичної реалізації.
Всі запити через EntityFramework повертають тільки IQueryable для отримання з БД тільки цільових данних.
Інтерфейс IQueryable дозволяє виконувати відкладені запити до БД без отримання додаткових пов’язаних з сутностями данних, що зменшує кількість виконаних SQL запитів.
Солушин прикладення містить 8 проектів
Hunter.Rest - основний startup project, що містить контролери, що слухають запити користувачів, і вюхи
Hunter.Tests - юніт-тести
Hunter.Services - вся логіка проекту
Hunter.Tools.LinkedIn - окремий проект під додаткові тулзи, зокрема, парсер для лінкдін профілю по посиланню (використовується при додаванні нового користувача)
Hunter.Common - використано для логування через пакет log4net
Для роботою з БД
Hunter.DataAccess.Entities - моделі і міграції
Hunter.DataAccess.Db - реалізація патерну репозиторій
Hunter.DataAccess.Interface - інтерфейси патерна репозиторій
При побудові компонентів проекту було використано ін’єкцію залежностей (dependency injection).
Це дало змогу:
- зручніше розширювати та змінювати проект,
- розірвати класи від їх залежностей для гнускості
- проводити юніт-тести з використання стабів і моків.
Ін’єкція залежностей була реалізована через конструктор. Було використано IoC контейер - бібліотека Ninject.
---------------------------------
Використання Ninject відбувається за допомогою классу StandardKernel, що сворюється за паттерном singleton і пов’язує задані інтерфейси із класами. Він відповідає за життєвий цикл екземплярів цих класів.
Dependency injection - шаблон проектування, що реалізує інверсію контролю (IoC Inversion Of Control).
Створення StandartKernerl відбувається через статичний метод :
public static StandardKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
kernel.Load(Assembly.GetExecutingAssembly());
kernel.Bind<IUserStore<User, int>>().To<HunterUserStore>();
kernel.Bind<IDatabaseFactory>().To<DatabaseFactory>().InRequestScope();
kernel.Bind<IUnitOfWork>().To<UnitOfWork>().InRequestScope();
…
return kernel;
}
Тут пов’язуються усі інтерфейси з необхідними класами.
Впровадження StandartKernel у додаток для його використання при ін’єкціях залежностей відбувається при конфігурації додатку :
public void ConfigureAuth(IAppBuilder app)
{
...
app.CreatePerOwinContext(CreateKernel);
app.UseNinjectMiddleware(CreateKernel);
…
}
Після цього можна використовувати ін’єкцію залежностей через конструктор. Приклад :
private readonly IPoolRepository _poolRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger _logger;
private readonly IActivityHelperService _activityHelperService;
public PoolService(IPoolRepository poolRepository, IUnitOfWork unitOfWork, ILogger logger, IActivityHelperService activityHelperService)
{
_poolRepository = poolRepository;
_unitOfWork = unitOfWork;
_logger = logger;
_activityHelperService = activityHelperService;
}
Далі поговоримо про найбільш цікаві частини проекту і рішення, які були використані для вирішення завдань.
На сьогоднішній день треба не тільки реагувати на користувацькі дії, але й створювати власні контроли, що зможуть самостійно це обробляти.
Таким чином, класична модель mvc відходить на другий план, та поступається місцем MVW (model view whatever).
В нашому проекті ми використовували Ангуляр.
Однією з найбільших переваг ангуляру є як раз створення директив, що по суті є новими контролами, що вміють відловлювати різноманітні івенти, відображати необхідну інформацію, виконувати обробку та як відображати дані у view, так і змінювати модель без візуальних змін.
Це дуже корисно із погляду перевикористання програмних можудів, оскільки ми маємо змогу створити новий тег, та просто вставити його у необхідних місцях. До того ж це економить час у суперечках (особливо у суперечках чи це mvc чи ні :)). “що це таке”. Називайте це як хочете або MVW.
Таким чином ми можемо:
міняти модель без зміни view та навпаки
спрощуємо unit-testing
спрощене перевикористання коду
коду менше, що призводить до меншої кількості багів.
Фронт-енд проекту реалізовано як single-page application. Використовується єдиний header, а необхідний html-файл визначається за допомогою angular routing і підгружається директивою ng-view. Було використано асинхронний підхід до запитів - AJAX. Результат запиту до серверної сторони приймається на клієнті як angular promise і використовується лише після отримання відповіді від серверу. Таким чином відображення змін на сторінці після отримання відповіді на запит відбувається без оновлення сторінки, що зменшує кількість трафіку і пришвидшує роботу програми.
Authentication is the process of ascertaining that somebody really is who he claims to be.
Authorization refers to rules that determine who is allowed to do what. E.g. Adam may be authorized to create and delete databases, while Usama is only authorised to read.
Наш сервер при кожному запиту перевіряє токен, перевіряє валідність, на основі інфи в токені визначає права доступу.
Token base авторизація, де з кожним запитом з клієнта відправляється Token.
Реалізовано це з допомогою бібліотеки jwt, яка визначає формат токена.
На сервері бібліотека Identity відповідає за перевірку токена. Для генерації токена – сервер генерації, що реалізовувала інша команда.
Для реалізації RESTfull API, зокрема, було використано протокол OData, а саме для отримання переліку вакансій і кандидатів, реалізації фільтрації та пагінації!
На клієнті треба сформувати url-запит відповідно до стандарту OData.
На сервері приймається запит контролером і перетворює його за допомогою інтерфейсу IQuarable у відповідних sql-запит для отримання лише потрібних даних з БД.
1. Open Data Protocol (OData) — это открытый RESTfull протокол для запроса и обновления данных. Протокол позволяет выполнять операции с ресурсами, используя в качестве запросов HTTP-команды, и получать ответы в форматах XML или JSON.
Оскільки структура прикладення Hunter була поділена на кілька незалежних шарів роботи з данними, то для зменшення залежності бібліотек, що входять до проекту, було прийнято рішення використовувати Data Transfer Objects (DTO) для обміну данними між частинами прикладення.
Зокрема, для роботи з базою.
Конвертація об’єктів бази данних у DTO об’єкти та навпаки була реалізована шляхом написання Extension методів для цільових классів.
Таким чином, конвертація сутностей БД в DTO об’єкти та навпаки виконувалась лище викликом одного extension метода:
return _poolRepository.Get(id).ToPoolViewModel();
Наведемо приклад для сутності БД Pool
Класс для роботи з БД
public class Pool : IEntity
{
public Pool()
{
Vacancy = new HashSet<Vacancy>();
Candidate = new HashSet<Candidate>();
}
public int Id { get; set; }
public string Name { get; set; }
public string Color { get; set; }
public virtual ICollection<Vacancy> Vacancy { get; set; }
public virtual ICollection<Candidate> Candidate { get; set; }
}
DTO модель
public class PoolViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Color { get; set; }
public ICollection<PoolBackground> PoolBackground { get; set; }
}
Extension
public static class PoolExtension
{
public static PoolViewModel ToPoolViewModel(this Pool pool)
{
return new PoolViewModel()
{
Id = pool.Id,
Name = pool.Name,
Color = pool.Color,
PoolBackground = PoolBackground.BgColors
};
}
public static IEnumerable<PoolViewModel> ToPoolViewModel(this IEnumerable<Pool> pools)
{
return pools.Select(p => new PoolViewModel()
{
Id = p.Id,
Name = p.Name,
Color = p.Color,
PoolBackground = PoolBackground.BgColors
}).ToList();
}
public static Pool ToPoolModel(this PoolViewModel poolView)
{
return new Pool
{
Id = poolView.Id,
Name = poolView.Name,
Color = poolView.Color,
Vacancy = new List<Vacancy>(),
Candidate = new List<Candidate>()
};
}
}
Юніт-тексти використовуються для створення ситуацій використання класів при певних умовах і перевірки правильності їх роботи, при цьому умови створюються лише необхідні для тесту умови за допомогою стабів.
В нашому проекті для проведення юніт-тестів було використано фреймворк NUnit і для створення стабів і моків було використано бібліотеку NSubstitue.
------------------------
Основним об’єктом для юніт-тестів був класс PublicPageParser, що відповідав за парсинг інформації зі сторінки профіля LinkedIn.
Приклад використання бібліотеки NSubstitute для створення стабів:
public void TestSetup()
{
var cardsList = new List<Card> {new Card {Id = 1, VacancyId = 1}, new Card {Id = 2, VacancyId = 1}};
_cardRepository.Query().Returns(cardsList.AsQueryable());
}
Приклад юніт-тесту з використання NUnit :
[Test]
public void Should_give_correct_skills_When_set_url()
{
// Arrange
var info = _parser.GetPageInfo("https://ua.linkedin.com/pub/myroslav-dmytrus/b7/a02/436");
// Act
var skills = info.Skills;
var checkSkill = new List<string>() {"C#",".NET","ASP.NET MVC","SQL","JavaScript","HTML","Microsoft Office"};
// Assert
Assert.AreEqual(checkSkill, skills);
}
Тут метод Assert фреймворку NUnit порівняє очікуваний результат з дійсним результатом і якщо вони рівні тест буде пройдено.
Отримати дані про особу з сервісу LinkedIn
Стандартним підходом вирішення даного питання є використання API. Але оскільки LinkedIn заборонив безкоштовний доступ до даних (є можливіть отримання інформації тільки про користувача якщо є логін та пароль).
Альтернативний спосіб отримання інформації є зчитування її напряму з HTML сторінки, який і був використаний.
Htmlagilitypack - зручний інструмент - Це HTML парсер, який дозволяє читання запис DOM і підтримує XPATH або XSLT. Це .NET бібліотека, яка дозволяє розбирати "з Web" HTML файли. Об'єктна модель дуже схожа на те, що пропонує System.Xml, але для HTML документів (або потоків).
Оскільки розмітка користувачів є однаковою це дає змогу написати стандартний підхід для всіх. Для отримання інформації зчитується весь HTML файл і для вибору необхідного використовується XPATH.
//отримання інформації про всі навички
var web = new HtmlWeb();
HtmlDocument Document = web.Load(Url);
var skills = Document.DocumentNode.SelectNodes("//span[@class='skill-pill']");
З допомогою даного підходу вдалося отримати всю публічну інформацію яка є доступна, а також посилання на фото. Недоліком є те що при зміні назв елементів буде втрачена можливість доступу до інформації.
На стороні користувача загалом все стандартно.
Як згадувалося, фреймворк Ангуляр використовується для реалізації СПА з боку клієнта.
Для розмітки сторінки - HTML5, CSS (оскільки дизайн сайту планується доробляти, тому для спрощення роботи було використано стилі Bootstrap).
Стильові ікон Font Awesome
Get all info on a certain vacancy and candidates included to this vacancy
Create new candidates, entering data by yourself or importing it from LinkedIn profile using public link
Create new candidates, entering data by yourself or importing it from LinkedIn profile using public link
Assigning statuses for candidates making work on them easier
Manage candidates progress through the hiring process stages, creating Long Lists, distinguishing most appropriate candidates after passing interviews and completing test tasks. Best candidates can also be selected to a special list for future applications using bookmarks mechanism.
Assign and Receive notification on “Not now” status candidates, with the information about recontacting candidate on a certain specified date.
Receive notifications for important actions made by you or your colleagues on Activity page.