ReactJS — современная популярная библиотека для построения Single Page Application. ReactJS позволяет создавать понятную и масштабируемую архитектуру веб-приложений, разбивать страницы на независимые компоненты, а также обеспечивает высокую скорость рендеринга. В то же время, ReactJS не описывает, каким образом должен выглядеть уровень «моделей» паттерна MVC и как должно быть организовано взаимодействие с бэкендом.
PonyORM — это объектно-реляционный маппер на языке Python, который позволяет описать модели сущностей и удобно работать с ними на бэкенде. Теперь, в дополнение к PonyORM, появилась библиотека PonyJS, которая позволяет автоматически генерировать аналогичные модели на фронтенде и работать с ними из языка JavaScript. Теперь вы можете написать запрос с помощью PonyORM, автоматически сериализовать данные в JSON, передать их на фронтенд и работать с ними как с полноценными объектами, а затем передать изменения обратно и сохранить их в базе данных.
В презентации рассказывается как можно создать одностраничное приложение на ReactJS, использующее объекты PonyORM/PonyJS в качестве моделей.
3. Задача
Разработать
Single
Page
Application
Приложение
-‐
библиотека
современного
искусства
Особенности
приложения:
1. Множество
связанных
объектов:
художники,
картины,
история
продаж,
выставки,
галлереи,
каталоги
и
т.д.
4. Задача
Разработать
Single
Page
Application
Приложение
-‐
библиотека
современного
искусства
Особенности
приложения:
1. Множество
связанных
объектов:
художники,
картины,
история
продаж,
выставки,
галлереи,
каталоги
и
т.д.
2. Нужна
мобильная
версия
приложения
(native
app)
5. Задача
Разработать
Single
Page
Application
Приложение
-‐
библиотека
современного
искусства
Особенности
приложения:
1. Множество
связанных
объектов:
художники,
картины,
история
продаж,
выставки,
галлереи,
каталоги
и
т.д.
2. Нужна
мобильная
версия
приложения
(native
app)
3. Разные
уровни
доступа:
обычные
пользователи,
платные
пользователи,
художники,
редакторы,
работники
галлерей
и
т.д.
6. 1.
Фронтенд.
Современные
фреймворки:
• Backbone
• AngularJS
• KnockoutJS
• EmberJS
• ReactJS
Выбор
технологий
7. 1.
Фронтенд.
Современные
фреймворки:
• Backbone
• AngularJS
• KnockoutJS
• EmberJS
• ReactJS
!
!
2.
Способ
передачи
данных
между
бэкендом
и
фронтендом
• REST
Выбор
технологий
8. 1.
Фронтенд.
Современные
фреймворки:
• Backbone
• AngularJS
• KnockoutJS
• EmberJS
• ReactJS
!
!
2.
Способ
передачи
данных
между
бэкендом
и
фронтендом
• REST
!
3.
Бэкенд
✓ Язык
-‐
Python
✓ База
данных
-‐
PostgreSQL
Выбор
технологий
9. Проблемы
1. Дублирование
моделей
на
фронтенде
(уже
есть
на
бэкенде)
2. REST
может
генерировать
слишком
много
запросов
к
серверу
3. Нет
двунаправленных
связей
между
объектами
на
фронтенде
(а
на
бэкенде
есть)
10. Решения
1. Генерировать
фронтенд
модели
автоматически
на
основе
моделей
на
бэкенде
!
1. Дублирование
моделей
на
фронтенде
(уже
есть
на
бэкенде)
2. REST
может
генерировать
слишком
много
запросов
к
серверу
3. Нет
двунаправленных
связей
между
объектами
на
фронтенде
(а
на
бэкенде
есть)
11. Решения
1. Генерировать
фронтенд
модели
автоматически
на
основе
моделей
на
бэкенде
!
2
и
3.
Передавать
связанные
объекты
в
одном
пакете
PonyJS
1. Дублирование
моделей
на
фронтенде
(уже
есть
на
бэкенде)
2. REST
может
генерировать
слишком
много
запросов
к
серверу
3. Нет
двунаправленных
связей
между
объектами
на
фронтенде
(а
на
бэкенде
есть)
13. Почему
PonyORM?
o1 = Order.objects.get(pk=1)
print(o1.total, o1.customer.id)
o2 = Order.objects.get(pk=2)
print(o2.total, o2.customer.id)
Django
ORM:
1.
Iden~tyMap
Customer
1
Customer
1
Order
1
Order
2
Order
1
Order
2
Customer
1
o1 = Order[1]
print(o1.total, o1.customer.id)
o2 = Order[2]
print(o2.total, o2.customer.id)
PonyORM:
Ac~ve
Record
Iden~ty
Map
14. session.query(Product).filter(
(Product.name.startswith('A')
&
(Product.image
==
None))
|
(extract('year',
Product.created_at)
<
2015))
Почему
PonyORM?
select(p
for
p
in
Product
if
p.name.startswith('A')
and
p.image
is
None
or
p.created_at.year
<
2015) Pony
Product.objects.filter(
Q(name__startswith='A',
image__isnull=True)
|
Q(created_at__year__lt=2015)) Django
SQLAlchemy
2.
Удобный
язык
запросов
15. База данных
Место
Pony
в
архитектуре
веб-‐приложения
PonyJS
Data Access
Business Logic
Service API
Приложение на сервере - сервис
Data Structures (ViewModels)
Business Logic
Presentation Layer
Браузер клиента
PonyORM
JSON JSON
Статический
HTML
25. class
Author(db.En~ty):
id
=
PrimaryKey(int,
auto=True)
name
=
Required(str)
artworks
=
Set("Artwork")
!
class
Artwork(db.En~ty):
id
=
PrimaryKey(int,
auto=True)
~tle
=
Required(str)
image
=
Required(str)
author
=
Required(Author)
У
объекта
Artwork
есть
связь
с
Author
44. Права
доступа
• Проверяются
на
сервере
при
выполнении
to_json()
и
from_json()
• Позволяют
спрятать
из
метаданных
классы
и
атрибуты
моделей,
которые
не
имеет
права
видеть
текущий
пользователь
45. Права
доступа
• Гарантируют,
что
на
фронтенд
передаются
только
объекты,
которые
можно
видеть
(row-‐level
permissions)
и
сериализуются
только
атрибуты,
которые
можно
видеть
• При
создании
и
изменении
объектов
проверяется
что
пользователь
имеет
права
на
создание/изменение
конкретного
экземпляра
объекта
и
конкретных
атрибутов
46. Способы
задания
прав
доступа
• На
уровне
классов
• На
уровне
объектов
(row-‐level
permissions)
Pony
предлагает
очень
удобный
декларативный
способ
задания
row-‐level
permissions
47. На
основе
чего
задаются
права?
• Группы
-‐
характеризуют
текущего
пользователя.
Примеры:
admin,
editor,
visitor
• Метки
-‐
описывают
свойства
отдельных
объектов.
Примеры:
public,
deleted
• Роли
-‐
описывают
взаимоотношения
пользователя
и
конкретного
объекта.
Примеры:
owner,
creator,
moderator
48. Пример
задания
прав
доступа
with db.permissions_for(Artwork):
allow('view’, group='anybody')
allow('create, edit', role='owner')
allow('edit, delete', group='admin')
49. Порядок
вычисления
прав
доступа
• Пони
запрашивает
группы
текущего
пользователя
и
кеширует
их
• Код
приложения
выбирает
объекты
запросом
и
вызывает
to_json()
• Пони
запрашивает
роли
текущего
пользователя
относительно
сериализуемых
объектов
• Код
приложения
вычисляет
роли
и
метки
и
сообщает
их
Пони
• Пони
проверяет
роли
на
соответствие
декларативно
заданным
правам
доступа
50. Использование
групп
для
задания
прав
with db.permissions_for(Artwork):
allow('view', group='anybody')
allow('create, edit', role='owner')
allow('edit, delete', group='admin')
52. Использование
ролей
для
задания
прав
with db.permissions_for(Artwork):
allow('view', group='anybody')
allow('create, edit', role='owner')
allow('edit, delete', group='admin')
53. Получение
ролей
текущего
пользователя
@roles_getter(Author, Artwork)
def get_author_roles(author, artwork):
if author is artwork.author:
return ['owner']
54. Проверка
роли
при
создании
объекта
with db.permissions_for(Artwork):
allow('view', group='anybody')
allow('create, edit', role='owner')
allow('edit, delete', group=‘admin')
!
Юзер при создании объекта не
сможет указать в поле
artwork.author другого юзера!
56. class
Author(db.En~ty):
id
=
PrimaryKey(int,
auto=True)
name
=
Required(str)
artworks
=
Set("Artwork")
!
class
Artwork(db.En~ty):
id
=
PrimaryKey(int,
auto=True)
~tle
=
Required(str)
image
=
Required(str)
author
=
Required(Author)
hidden
=
Required(bool,
default=False)
Пример
использования
меток
57. Пример
использования
меток
with db.permissions_for(Artwork):
allow('view', group='anybody',
label='public')
allow('create, edit', role='owner')
allow('edit, delete', group='admin')
64.
Отключение
автоматических
фильтров
with default_filters_disabled:
artworks = Artwork.select(
lambda a: a.author.name == 'Gerhard Richter'
).order_by(lambda a: a.title)[:3]
!
SELECT "a"."id", "a"."title", "a"."image",
"a"."hidden", "a"."author"
FROM "Artwork" "a"
INNER JOIN "Author" "author-1"
ON "a"."author" = "author-1"."id"
WHERE "author-1"."name" = 'Gerhard Richter'
ORDER BY "a"."title"
LIMIT 3
65. Разные
права
для
разных
сайтов
with db.permissions_for(Artwork):
allow('view', group='anybody',
label='public', site='public')
allow('create, edit', role='owner',
site='admin')
allow('edit, delete', group='admin',
site='admin')
66. ReactJS
• ReactJS
позволяет
строить
страницу
из
компонентов
• Компоненты
представляют
собой
слой
View
• Получают
свойства
(props)
и
используют
их
при
рендеринге
• Объекты
PonyJS
можно
использовать
в
качестве
значений
свойств
компонентов
67. Пример
React-‐компонента
var ArtworkDescription = React.createClass({
render: function () {
var artwork = this.props.artwork;
return <div>
<h2>{ artwork.title() }</h2>
<img src={ artwork.image() } />
<p>{ artwork.author().name() }</p>
</div>
}
});
68. Пример
React-‐компонента
var ArtworksPage = React.createClass({
render: function () {
var artworkList = …
return <div>
<h1>Artwork list</h1>
<ul>{ artworkList }</ul>
</div>
}
});
69. Фрагменты
кода
с
ReactJS
var artworkList = _.map(
this.props.artworks, function (item) {
return <li key={ item.id() }>
<ArtworkDescription artwork={ item } />
</li>
});
70. Фрагменты
кода
с
ReactJS
var ArtworksPage = React.createClass({
render: function () {
var artworkList = _.map(
this.props.artworks, function (item) {
return <li key={ item.id() }>
<ArtworkDescription artwork={ item } />
</li>
});
return <div>
<h1>Artwork list</h1>
<ul>{ artworkList }</ul>
</div>
}
});
71. Фрагменты
кода
с
ReactJS
var ArtworksPage = React.createClass({
render: function () {
return <div>
<h1>Artwork list</h1>
<ul>{
_.map(this.props.artworks, function (item) {
return <li key={ item.id() }>
<ArtworkDescription artwork={ item } />
</li>
});
}</ul>
</div>
}
});
73. • Автогенерация
моделей
на
фронтенде
• Сериализация
произвольных
данных,
включая
двусторонние
связи
many-‐to-‐many
• Объекты
с
двунаправленными
связями
на
клиенте
• Может
использоваться
с
любым
фронтенд-‐
фреймворком
(React,
Angular,
Knockout
и
др.)
• Формат
передаваемых
данных
универсален.
Может
использоваться
с
другими
бэкендами
• Двунаправленный
биндинг
• Передача
изменений
обратно
на
сервер
• Декларативные
права
доступа
на
уровне
объектов
Заключение.
Что
предлагает
PonyJS?