Как стать автором
Обновить
870.91
OTUS
Цифровые навыки от ведущих экспертов

Основы проектирования архитектуры простой социальной сети

Уровень сложностиПростой
Время на прочтение15 мин
Количество просмотров10K

Социальные сети стали неотъемлемой частью нашей повседневной жизни. Они объединяют людей, позволяют обмениваться информацией, поддерживать связь с друзьями и даже находить новых знакомых. Однако, за всеми этими возможностями стоит сложная инженерная работа по созданию и поддержанию социальных платформ.

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

Немного анализа требований

  1. Функциональные требования

    Прежде чем мы начнем проектировать архитектуру нашей социальной сети, необходимо четко определить функциональные требования:

    • Регистрация и аутентификация: Какие методы регистрации будут предоставлены пользователям (например, через социальные сети или электронную почту)? Как будет осуществляться аутентификация пользователей?

    • Профили пользователей: Какие данные будут храниться в профилях пользователей? Как пользователи могут настраивать свои профили?

    • Создание и управление контентом: Как пользователи могут создавать и публиковать контент (текст, фотографии, видео)? Как будет управляться доступ к контенту?

    • Социальные связи: Как пользователи могут устанавливать связи между собой (друзья, подписчики)? Какие функции для взаимодействия будут предоставлены?

    • Уведомления: Какие уведомления будут отправляться пользователям (например, уведомления о новых сообщениях или публикациях друзей)?

  2. Нефункциональные требования

    Помимо функциональных требований, необходимо учесть нефункциональные аспекты, которые влияют на качество и производительность системы. Вот некоторые из них:

    • Масштабируемость: Сеть может иметь миллионы пользователей, поэтому система должна быть способной масштабироваться горизонтально при необходимости.

    • Безопасность: Какие меры безопасности будут применены для защиты данных пользователей и системы от внешних атак?

    • Производительность: Система должна обеспечивать высокую производительность, чтобы пользователи могли быстро получать доступ к контенту и взаимодействовать друг с другом.

    • Доступность: Сеть должна быть доступной 24/7, и должны быть предусмотрены меры для минимизации времени простоя.

Проектирование архитектуры

Проектирование архитектуры для социальной сети начинается с осознанного выбора технологического стека:

  1. Базы данных

    • SQL (реляционные базы данных): Примеры - PostgreSQL, MySQL, SQLite.

      Преимущества:

      • Структурированные данные и возможность поддерживать целостность данных.

      • Сложные запросы для анализа и отчетности.

      • Подходят для приложений, где данные имеют жесткую структуру, такую как профили пользователей и социальные связи.

    • NoSQL (нереляционные базы данных): Примеры - MongoDB, Cassandra, Redis.

      Преимущества:

      • Гибкая структура данных, хорошо подходит для хранения разнородной информации, например, новостной ленты.

      • Масштабируемость и высокая производительность для больших объемов данных.

      • Возможность быстро добавлять новые поля и типы данных.

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

    Создание таблицы пользователей в PostgreSQL:

    CREATE TABLE users (
        user_id SERIAL PRIMARY KEY,
        username VARCHAR(255) NOT NULL,
        email VARCHAR(255) NOT NULL UNIQUE,
        password_hash VARCHAR(255) NOT NULL
    );
    

    Пример кода для добавления пользователя в MongoDB:

    db.users.insert({
        username: "example_user",
        email: "user@example.com",
        password_hash: "hashed_password"
    });
    
  2. Языки программирования

    Выбор языка программирования также имеет важное значение. Языки программирования, которые широко используются в разработке социальных сетей:

    • Python: Python предоставляет множество фреймворков для веб-разработки, таких как Django и Flask. Он известен своей простотой и читаемостью кода.

    • JavaScript: JavaScript используется для разработки фронтенда и бэкенда с использованием Node.js. Он позволяет создавать интерактивные веб-приложения.

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

    Пример кода для создания простого веб-сервера на Node.js с использованием Express:

    const express = require("express");
    const app = express();
    
    app.get("/", (req, res) => {
        res.send("Привет, мир!");
    });
    
    app.listen(3000, () => {
        console.log("Сервер запущен на порту 3000");
    });
    
  3. Фреймворки и библиотеки

    Фреймворки и библиотеки значительно ускоряют процесс разработки и обеспечивают структуру проекта. Вот несколько популярных фреймворков и библиотек:

    • Django: Python-фреймворк, предоставляющий множество инструментов для разработки социальных сетей, включая аутентификацию и административный интерфейс.

    • React: JavaScript-библиотека для создания интерактивных пользовательских интерфейсов, которую можно использовать для фронтенда социальной сети.

    • Express: Минималистичный Node.js-фреймворк для создания быстрых и масштабируемых веб-приложений.

    • Socket.IO: Библиотека для реализации реального времени в чате и уведомлениях.

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

Структура базы данных

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

  1. Пользователи и профили

    Пользователи - это центральное звено любой социальной сети. Для хранения информации о пользователях, их профилях и настройках, мы можем создать таблицу users или коллекцию users, в зависимости от выбранной базы данных. Пример структуры для таблицы users:

    CREATE TABLE users (
        user_id SERIAL PRIMARY KEY,
        username VARCHAR(255) NOT NULL,
        email VARCHAR(255) NOT NULL UNIQUE,
        password_hash VARCHAR(255) NOT NULL,
        bio TEXT,
        profile_picture_url VARCHAR(255),
        registration_date TIMESTAMP DEFAULT NOW()
    );
    

    В данной таблице мы храним информацию о пользователе, такую как имя пользователя (username), адрес электронной почты (email), хэш пароля (password_hash), биографию (bio), URL профильной фотографии (profile_picture_url) и дату регистрации (registration_date).

    Код для вставки нового пользователя в таблицу:

    INSERT INTO users (username, email, password_hash)
    VALUES ('john_doe', 'john@example.com', 'hashed_password');
    
  2. Друзья и связи между пользователями

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

    Например, для хранения списка друзей пользователей, мы можем создать таблицу user_friends:

    CREATE TABLE user_friends (
        user_id INT,
        friend_id INT,
        PRIMARY KEY (user_id, friend_id),
        FOREIGN KEY (user_id) REFERENCES users (user_id),
        FOREIGN KEY (friend_id) REFERENCES users (user_id)
    );
    

    В данной таблице каждая запись представляет собой связь между пользователем (user_id) и его другом (friend_id).

    Код для добавления друга для пользователя с user_id = 1:

    INSERT INTO user_friends (user_id, friend_id)
    VALUES (1, 2);
    
  3. Публикации и контент

    Для хранения информации о публикациях мы можем создать таблицу posts:

    CREATE TABLE posts (
        post_id SERIAL PRIMARY KEY,
        user_id INT,
        content TEXT,
        created_at TIMESTAMP DEFAULT NOW(),
        FOREIGN KEY (user_id) REFERENCES users (user_id)
    );
    

    В данной таблице каждая запись представляет собой одну публикацию, содержащую информацию о пользователе (user_id), текстовом контенте (content) и дате создания (created_at).

    Код для создания новой публикации от пользователя с user_id = 1:

    INSERT INTO posts (user_id, content)
    VALUES (1, 'Привет, мир! Это моя первая публикация.');
    
  4. Лента новостей и фиды

    Для формирования ленты новостей пользователей и их фидов (ленты контента от друзей), можно использовать разные подходы. Одним из распространенных методов является кэширование и предварительное вычисление ленты. Например, мы можем использовать таблицу news_feed для хранения уже подготовленных записей в ленте:

    CREATE TABLE news_feed (
        user_id INT,
        post_id INT,
        created_at TIMESTAMP DEFAULT NOW(),
        FOREIGN KEY (user_id) REFERENCES users (user_id),
        FOREIGN KEY (post_id) REFERENCES posts (post_id)
    );
    

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

    Код для добавления записи в ленту пользователя с user_id = 1:

    INSERT INTO news_feed (user_id, post_id)
    VALUES (1, 5);  -- 5 - это идентификатор публикации
    
  5. Чат и мессенджер

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

    Пример кода для создания таблицы messages, содержащей сообщения между пользователями:

    CREATE TABLE messages (
        message_id SERIAL PRIMARY KEY,
        sender_id INT,
        receiver_id INT,
        content TEXT,
        sent_at TIMESTAMP DEFAULT NOW(),
        FOREIGN KEY (sender_id) REFERENCES users (user_id),
        FOREIGN KEY (receiver_id) REFERENCES users (user_id)
    );
    

    В данной таблице хранятся сообщения (content), отправленные от отправителя (sender_id) к получателю (receiver_id) с указанием времени отправки (sent_at).

    Пример кода для отправки сообщения от пользователя с sender_id = 1 пользователю с receiver_id =2:

INSERT INTO messages (sender_id, receiver_id, content)
VALUES (1, 2, 'Привет, как дела?');

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

Компоненты системы

Компоненты системы для социальной сети обеспечивают ее функциональность.

  1. Аутентификация и авторизация

    Аутентификация и авторизация - это первоочередные компоненты для обеспечения безопасности и управления доступом пользователей к ресурсам социальной сети:

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

      from flask import Flask, request, jsonify
      from flask_jwt_extended import create_access_token, JWTManager
      
      app = Flask(__name__)
      app.config['JWT_SECRET_KEY'] = 'your-secret-key'
      jwt = JWTManager(app)
      
      @app.route('/login', methods=['POST'])
      def login():
          username = request.json.get('username')
          password = request.json.get('password')
      
          # Проверка логина и пароля (здесь можно использовать базу данных)
          if username == 'user' and password == 'password':
              access_token = create_access_token(identity=username)
              return jsonify(access_token=access_token), 200
          else:
              return jsonify(message='Неверные учетные данные'), 401
      
      if __name__ == '__main__':
          app.run()
      
    • Авторизация: Этот компонент определяет, какие действия и ресурсы пользователь может использовать после аутентификации. Например, доступ к личной странице пользователя и редактирование ее данных должны быть разрешены только ему.

  2. Лента новостей и фиды

    Лента новостей и фиды позволяют пользователям видеть контент от других пользователей, на которых они подписаны, а также собственные публикации:

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

    • Управление контентом: Пользователи должны иметь возможность создавать и редактировать свой контент, а также взаимодействовать с контентом других пользователей (например, лайки и комментарии).

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

  3. Чат и мессенджер

    Функциональность чата и мессенджера позволяет пользователям общаться в режиме реального времени. Основные аспекты:

    • Приватные и групповые чаты: Система должна поддерживать как одиночные чаты между пользователями, так и групповые чаты с несколькими участниками.

    • Уведомления: Пользователи должны получать уведомления о новых сообщениях и событиях в чате, даже если они не активны на сайте.

    • Хранение сообщений: Сообщения могут быть хранены в базе данных для последующего просмотра и поиска.

    Пример кода для отправки сообщения в чате на JavaScript с использованием библиотеки Socket.IO:

    // Сервер
    const io = require('socket.io')(server);
    
    io.on('connection', (socket) => {
        socket.on('message', (data) => {
            // Обработка и сохранение сообщения в базе данных
            io.emit('message', data);  // Отправка сообщения всем подключенным клиентам
        });
    });
    
    // Клиент
    const socket = io.connect('http://example.com');
    
    socket.emit('message', {
        sender: 'user1',
        content: 'Привет, как дела?'
    });
    
  4. Поиск и фильтрация контента

    Для поиска и фильтрации контента можно использовать инструменты, такие как Elasticsearch.Пример кода для выполнения поискового запроса с использованием Elasticsearch на Python с помощью библиотеки Elasticsearch-py:

    from elasticsearch import Elasticsearch
    
    # Подключение к Elasticsearch
    es = Elasticsearch([{'host': 'localhost', 'port': 9200}])
    
    # Пример поискового запроса
    def search_content(query):
        results = es.search(index='posts', body={
            'query': {
                'match': {
                    'content': query
                }
            }
        })
        return results['hits']['hits']
    
    # Пример использования
    search_results = search_content('интересная статья')
    
  5. Уведомления и оповещения

    Для отправки уведомлений и оповещений можно использовать библиотеки и сервисы, такие как Firebase Cloud Messaging (FCM) для пуш-уведомлений. Пример кода для отправки пуш-уведомления на Python с использованием библиотеки PyFCM:

    from pyfcm import FCMNotification
    
    # Инициализация FCM
    push_service = FCMNotification(api_key="your-api-key")
    
    # Отправка уведомления
    registration_id = "device-registration-id"
    message_title = "Новое уведомление"
    message_body = "У вас новое сообщение в чате."
    result = push_service.notify_single_device(
        registration_id=registration_id,
        message_title=message_title,
        message_body=message_body
    )
    
  6. Аналитика и мониторинг

    Для сбора данных о пользовательской активности и производительности системы можно использовать различные инструменты. Пример кода для логирования событий с использованием библиотеки logging:

    import logging
    
    # Конфигурация логирования
    logging.basicConfig(filename='app.log', level=logging.INFO)
    
    # Запись события в лог
    logging.info('Пользователь john_doe выполнил вход в систему.')
    

    Для анализа логов и мониторинга производительности можно использовать инструменты, такие как Grafana и Prometheus.

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

Масштабируемость и производительность

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

  1. Горизонтальное масштабирование

    Горизонтальное масштабирование предполагает распределение нагрузки путем добавления дополнительных серверов или узлов в систему. Это позволяет увеличить производительность и обработку запросов.

    Пример кода на Python для создания распределенной системы с использованием библиотеки Flask и Redis в качестве брокера задач:

    from flask import Flask
    from rq import Queue
    from redis import Redis
    
    app = Flask(__name__)
    redis_conn = Redis()
    
    # Создание очереди задач
    task_queue = Queue(connection=redis_conn)
    
    @app.route('/process', methods=['POST'])
    def process_data():
        data = request.json
        # Отправка задачи на выполнение в очередь
        task = task_queue.enqueue(data_processing_function, data)
        return jsonify({'task_id': task.get_id()}), 202
    
    if __name__ == '__main__':
        app.run()
    

    В данном примере мы использовали очередь задач (в данном случае, с помощью библиотеки RQ), чтобы асинхронно обрабатывать запросы и разгрузить сервер.

  2. Кэширование и оптимизация запросов

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

    Пример кода для кэширования запросов с использованием библиотеки Python cachetools:

    from cachetools import TTLCache
    
    # Создание кэша с временем жизни 60 секунд
    cache = TTLCache(maxsize=100, ttl=60)
    
    def get_data_from_cache(key):
        # Попытка получить данные из кэша
        data = cache.get(key)
        if data is not None:
            return data
        else:
            # Если данных нет в кэше, получаем их из базы данных
            data = fetch_data_from_database(key)
            # Сохранение данных в кэше
            cache[key] = data
            return data
    

    Кроме кэширования, оптимизация запросов к базе данных также играет важную роль в обеспечении высокой производительности. Это может включать в себя индексацию таблиц, оптимизацию SQL-запросов и использование баз данных, спроектированных с учетом специфики запросов.

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

Безопасность и защита данных

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

  1. Защита от атак

    SQL-инъекции: SQL-инъекции возникают, когда злоумышленник вставляет злонамеренный SQL-код в пользовательский ввод, чтобы получить доступ к базе данных или изменить данные. Для предотвращения SQL-инъекций используйте параметризованные запросы и ORM (Object-Relational Mapping), если это возможно. Пример с использованием ORM SQLAlchemy в Python:

    from sqlalchemy import text
    
    # Плохой способ (подвержен SQL-инъекциям)
    user_input = "'; DROP TABLE users;"
    query = f"SELECT * FROM users WHERE username = '{user_input}'"
    
    # Хороший способ (использование параметров)
    query = text("SELECT * FROM users WHERE username = :username")
    result = db.session.execute(query, {"username": user_input})
    

    Кросс-сайтовый скриптинг (XSS): XSS-атаки возникают, когда злоумышленник внедряет вредоносный JavaScript-код в веб-страницы, который выполняется в браузере других пользователей. Для защиты от XSS используйте функции экранирования вывода и корректную настройку заголовков Content Security Policy (CSP).

    // Плохой способ (подвержен XSS)
    const user_input = "<script>alert('Вредоносный код');</script>";
    document.getElementById("output").innerHTML = user_input;
    
    // Хороший способ (использование функции экранирования)
    const user_input = "<script>alert('Вредоносный код');</script>";
    document.getElementById("output").textContent = user_input;
    
  2. Шифрование данных

    Шифрование в покое: Для защиты данных в покое (например, паролей пользователей и конфиденциальных сообщений) используйте алгоритмы шифрования. HTTPS (TLS/SSL) обеспечивает защиту данных между клиентом и сервером.

    Пример использования HTTPS с использованием библиотеки OpenSSL в Node.js:

    const https = require('https');
    const fs = require('fs');
    
    const options = {
        key: fs.readFileSync('private-key.pem'),
        cert: fs.readFileSync('public-cert.pem')
    };
    
    const server = https.createServer(options, (req, res) => {
        res.statusCode = 200;
        res.setHeader('Content-Type', 'text/plain');
        res.end('Hello, secure world!\n');
    });
    
    server.listen(443);
    
  3. Аутентификация и авторизация

    Сильная аутентификация: Пароли пользователей должны быть хешированы с использованием сильных хэш-функций. Дополнительные меры безопасности могут включать в себя двухфакторную аутентификацию (2FA).

    Пример хэширования пароля на Python с использованием библиотеки bcrypt:

    import bcrypt
    
    password = "secure_password"
    hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
    

    Авторизация: Убедитесь, что пользователи имеют только те права доступа, которые необходимы для выполнения их задач. Реализуйте систему ролей и прав доступа.

  4. Санкционированный доступ и аудит

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

    Защита API: Если у вас есть API, используйте механизмы аутентификации и выдачи токенов (например, JWT) для обеспечения безопасного доступа к данным и функциям.

  5. Обновления и патчи

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

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

Всегда лучше предотвращать угрозы, чем реагировать на них.

Разработка серверной части

Прежде всего, убедитесь, что у вас установлены необходимые библиотеки. Вы можете установить их с помощью pip:

pip install flask flask-pymongo flask-restful Flask-JWT-Extended

Пример кода для создания серверной части:

from flask import Flask, request
from flask_restful import Api, Resource, reqparse
from flask_pymongo import PyMongo
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity

app = Flask(__name__)
api = Api(app)
app.config['MONGO_URI'] = 'mongodb://localhost:27017/social_network'
app.config['JWT_SECRET_KEY'] = 'super-secret-key'
mongo = PyMongo(app)
jwt = JWTManager(app)

# Создание модели пользователя
class User(Resource):
    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument('username', required=True)
        parser.add_argument('password', required=True)
        args = parser.parse_args()
        
        existing_user = mongo.db.users.find_one({'username': args['username']})
        if existing_user:
            return {'message': 'Пользователь с таким именем уже существует'}, 400
        
        user_id = mongo.db.users.insert({'username': args['username'], 'password': args['password']})
        return {'message': 'Пользователь зарегистрирован', 'user_id': str(user_id)}, 201

# Создание ресурсов API
api.add_resource(User, '/user')

if __name__ == '__main__':
    app.run(debug=True)

Разработка клиентской части

Клиентская часть социальной сети обычно реализуется с использованием веб-технологий, таких как HTML, CSS и JavaScript. В зависимости от требований, вы также можете использовать фреймворки, такие как React, Angular или Vue.js.

Пример кода для простой веб-страницы с использованием HTML и JavaScript:

<!DOCTYPE html>
<html>
<head>
    <title>Простая социальная сеть</title>
</head>
<body>
    <h1>Добро пожаловать в социальную сеть</h1>
    <div id="posts">
        <!-- Здесь будут отображаться посты пользователей -->
    </div>
    <textarea id="post-content" placeholder="Напишите свой пост"></textarea>
    <button onclick="createPost()">Опубликовать</button>

    <script>
        function createPost() {
            const content = document.getElementById('post-content').value;
            // Отправка поста на сервер
            // ...
        }

        // Запрос на сервер для загрузки постов
        // ...
    </script>
</body>
</html>

Тестирование и отладка

Тестирование является важной частью разработки. Вы можете использовать фреймворки для тестирования, такие как PyTest для Python или Jest для JavaScript, чтобы написать юнит-тесты, интеграционные тесты и тесты API.

Пример юнит-теста с использованием PyTest для серверной части:

import app  # Импорт вашего приложения

def test_create_user():
    client = app.app.test_client()
    response = client.post('/user', json={'username': 'testuser', 'password': 'testpassword'})
    assert b'Пользователь зарегистрирован' in response.data

Внедрение и деплоймент

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

Пример деплоймента на сервере с использованием инструмента управления контейнерами Docker:

  1. Создайте Dockerfile для вашего приложения:

FROM python:3.9

WORKDIR /app

COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt

COPY . .

CMD ["python", "app.py"]
  1. Соберите Docker-образ:

docker build -t social-network-app .
  1. Запустите контейнер:

docker run -p 5000:5000 social-network-app

Ваше приложение теперь будет доступно на порту 5000 на сервере.

Заключение

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

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

Статья подготовлена в рамках запуска курса Enterprise Architect. Хочу порекомендовать вам бесплатные уроки курса, на которые вы можете зарегистрироваться прямо сейчас:

Теги:
Хабы:
Всего голосов 18: ↑14 и ↓4+12
Комментарии5

Публикации

Информация

Сайт
otus.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
OTUS