MongoDb for developers. Неделя 1

Источник: habrahabr
sl4mmer

В этой статье будет изложен основной материал первой недели обучения. Если аудитория проявит интерес - то подобные посты будут выходить в конце каждой недели.

Мы вкратце рассмотрим, что представляет собой MongoDB, сравним разницу в структурах данных между монго и реляционными базами для простого веб-приложения, поиграемся с шеллом, и немножко покодим на пхп и питоне.

Зачем эта статья? Предвижу подобный вопрос. Не все успели записаться на курсы, не у всех есть достаточно свободного времени, не у всех хорошо обстоят дела с восприятием устной английской речи. Ну и для гуглящих подобный материал не помешает. 

МонгоДб - не реляционная база данных, предназначенная для хранения JSON-документов. Рассмотрим некий абстрактный экземпляр коллекции монго
{a:3 b:7 fruit:["apple","banana","peach"]} 
Мы видим, что данный документ имеет целочисленные ключи a и b и ключ fruit который является массивом. В реляционных базах подобное поведение невозможно. Стоит отметить, что подобное представление данных гораздо ближе к коду, чем то с которым мы имеем дело работая с реляционными базами данных. 

Коллекции в МонгоДб не привязаны к заранее определенной схеме (schemaless). Например следующие документы, могут быть элементами одной коллекции
{a:1,b2} {a:2,b:4,c:10} 
Вспомните, сколько раз вам приходилось скрипя зубами делать ALTER TABLE, добавляя новое поле, которое то и использовать будут далеко не все элементы? С МонгоДБ подобное осталось в прошлом.

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

Одна из основополагающих идей MongoDb - предоставлять широкий набор возможностей, при сохранении высокой производительности. Конечно чем-то приходится жертвовать. Так например Монго не имеет аналога join: нельзя объединить элементы двух разных коллекций. Вторым важным отличием от реляционных баз данных является отсутствие транзакций. Последнее звучит пугающе, но дело в том, что в монго вам не понадобятся транзакции, в тех ситуациях где они были бы необходимы при использовании реляционной базы.

От слов к делу
Для продолжения нам потребуется установить MongoDb. Вы скорее всего найдете свежую версию в репозитории своего дистрибутива, в случае отсутствия таковой- исходники доступны на официальном сайте. Там же есть бинарники для win и mac. Стоит отметить, что 32х-битная версия имеет существенные ограничения по объему хранимых данных, так что ее можно использовать только для разработки. Сейчас для нас это не принципиально.
Устанавливаем, запускаем и заходим в шелл.

dirtyhack@dirtyhack-ThinkPad-Edge-E125:~$ mongo
MongoDB shell version: 2.0.4
connecting to: test
> 

Пока у нас нет никаких данных, монго создал для нас базу test, с которой мы и будем работать. Положим в нее наш первый элемент

> db.users.save({name:"Woody",age:23});
> db.users.find().pretty()
{
    "_id" : ObjectId("508ea7f33cc5578ed9ecbf46"),
    "name" : "Woody",
    "age" : 23
}
> 

Тут сразу много интересного. Командой db.users.save() мы отправили запрос на сохранение документа в коллекцию users текущей базы данных. Ранее этой коллекции не существовало, она была автоматически создана при запросе.

> show collections
system.indexes
users
>

Мы сохранили в коллекции users простой json-документ, с ключами name и age. О назначении команды find, догадаться несложно - стоит отметить, что в случае когда нас интересует только один документ стоит пользоваться командой findOne(). Команда pretty() не влияет на логику- она просто выводит результат в удобном для чтения виде - без нее, мы бы получили строку, что не удобно при работе со сложными объектами.

Пришло время добавить в коллекцию элемент посложнее, и наглядно продемонстрировать безсхемность монго.

> db.users.save({name:"Bazz",age:23,place_of_birth: {country:"unknown",region:"unknown"},interests:["flying","fighting with evil"]});
> db.users.find({name:"Bazz"}).pretty()
{
    "_id" : ObjectId("508eab333cc5578ed9ecbf47"),
    "name" : "Bazz",
    "age" : 23,
    "place_of_birth" : {
        "country" : "unknown",
        "region" : "unknown"
    },
    "interests" : [
        "flying",
        "fighting with evil"
    ]
}
> 

Второй добавленный нами документ уже более похож на реальные данные. Обратите внимание, что значениями элементов place_of_birth и interests являются вложенные документы- словарь (JSONObject) и массив (JSONArray) соответственно. Иерархическая вложенность - ограничивается здравым смыслом и лимитом в 16 мегабайт на элемент. 

Кстати, мы можем исgользовать встроенный интерпретатор javascript

> var j=db.users.findOne({name:"Woody"})
> j.age=25
25
> db.users.save(j)
> db.users.find({name:"Woody"}).pretty()
{
    "_id" : ObjectId("508ea7f33cc5578ed9ecbf46"),
    "name" : "Woody",
    "age" : 25
}
> 

Вы наверное уже заметили, что каждому элементу присваивается идентификатор _id. О нем мы поговорим несколько позже, отметим только, что он уникален в пределах одной коллекции.

Полученную коллекцию мы используем в небольшом приложении, в завершении статьи.

Проектирование
Рассмотрим как будет выглядеть структура данных простого веб-приложения в Монго и реляционной базе данных.

Представим, что у нас есть блог с базовым функционалом. Авторы могут публиковать посты, прикрепляя к ним теги, пользователи могут искать по тегам и оставлять к постам комментарии.

При использовании реляционной базы данных нам понадобятся таблицы для хранения данных об авторах, постах и комментариях. Так же нам, скорее всего, понадобятся дополнительные таблицы для хранения связей между таблицами. Конечно сообразно своим реалиям, вы можете денормализовать базу блога, но в базовом виде, она будет выглядеть как-то так.

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

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

Пиши код, твою мать.

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

Ввиду элементарности действий, мы не будем создавать никаких абстракций и сделаем все в лоб (коллеги наверное подумают, что я заболел). Думаю писать дао-прослойки вы прекрасно умеете сами.

Мы создадим простое веб-приложение выводящее на экран имена и возраст пользователей из коллекции users и позволяющее добавлять в коллекцию новые элементы.

Для запуска примера на питоне вам потребуется драйвер pymongo и фреймворк bottle. Для запуска примера на php, вам так же потребуется драйвер, его можно взять из pear(незабудьте добавить mongo.so в список расширений).

Итак примеры
Python
index.py

import bottle
import pymongo
import sys
# Открываем безопасное соединение с локальной базой
# При работе в безопасном режиме, в случае ошибки будет выброшено исключение, но их обработку мы рассмотрим позже
# там все просто
connection=pymongo.Connection("mongodb://localhost",safe=True)
# будем работать с нашей тестовой базой
db=connection.test

@bottle.route('/')
def home_page():
    #Получаем  все элементы коллекции
    users=db.users.find();
    return bottle.template('index',{"users":users})

@bottle.post('/submit')
def submit_page():
    # подготавливаем элемент из данных формы
    user={'name':bottle.request.forms.get("name"),'age':bottle.request.forms.get("age")}
    #Сохраняем
    db.users.insert(user)
    bottle.redirect('/')

bottle.debug()
bottle.run(host="localhost",port=8080)

views/index.tpl

<!DOCTYPE html>
<html>
<head>
    <title>Hello world</title>
</head>
<body>
<p>
   Список пользователей
</p><ul>
    %for user in users:
    <li>Имя:{{user['name']}} Возраст:{{user['age']}}</li>
    %end
</ul>
<form action="/submit" method="POST">
   Добавить нового пользователя<br>
    <input type="text" name="name" size=40 value=""><br>
    <input type="text" name="age" size=40 value=""><br>
    <input type="submit" value="submit">

</form>

</body>
</html>

PHP
Пример на пхп отличается редкой незатейливостью

<?  
    $mongo = new Mongo('localhost');
    $database = $mongo -> test;     
    if ($_POST)
        $database -> users -> insert($_POST);
    
    $users = $database -> users -> find();
?>
<!DOCTYPE html>
<html>
<head>
    <title>Hello world</title>
</head>
<body>
<?var_dump($users)?>
<p>
   Список пользователей
</p><ul>
    <?foreach($users as $user){?>
    <li>Имя:<?=$user['name']?> Возраст:<?=$user['age']?></li>
<?}?>
</ul>
<form action="/" method="POST">
   Добавить нового пользователя<br>
    <input type="text" name="name" size=40 value=""><br>
    <input type="text" name="age" size=40 value=""><br>
    <input type="submit" value="submit">

</form>
</body>
</html>

Важный вопрос к тем, кто заинтересован в примерах на php. В дальнейшем код будет посложнее, можно использовать Yii или ZF, либо самописный микрофреймворк - жду ваших пожеланий.

На этом все, надеюсь не зря старался. Если статья понравится хабражителям - на след неделе, опубликую отчет по второй неделе, на которой будут рассматриваьтся CRUD операции.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=30892