3.6.3 Описание выбранного решения
Было принято решение внедрить необходимый мета-тип (по крайней мере, на стадии пилотного проекта) прямо в конфигурационный файл schema.py, где создается абстрактная заявка. Таким образом, мы также будем создавать абстрактную заявку, но уже не конкретного типа, а мета-типа. Roundup будет работать с ней, как с обычной абстрактной заявкой, однако, при помощи разрабатываемого решения разработчик шаблонов будет специфицировать конкретный тип заявок, используя для этого либо уже имеющиеся заранее (например, указанные в том же файле schema.py) интерфейсы доступа, либо создавая их в динамике прямо во время работы системы, используя для этого возможности библиотеки Pli и разрабатываемого расширения.
В качестве расширения для мета-типа был использован pli.interface.objects.ObjectWithInterface уже содержащий в себе интерфейс (в терминологии pli.interface.interface). Был создан класс IssueObjectWithInterface унаследованный от IssueClass (класс, реализующий интерфейс) и указанного выше расширения. К сожалению, из-за технических тонкостей наследоваться прямо от IssueClass нельзя. Это вызвано тем, что интерфейс внутри объекта ObjectWithInterface создается при помощи метода __new__ создающего объект instance object ObjectWithInterface, однако далее должен вызываться конструктор __init__ класса IssueClass, который первым параметром должен получать объект типа instance object IssueClass. Для решения этой проблемы пришлось создать промежуточный класс (ProxyToIssueClass) унаследованный от IssueClass и с определенным конструктором __init__ в котором вызывается конструктор __init__ класса IssueClass (к сожалению, так как в Roundup используются только old-style классы __init__, приходится вызывать от IssueClass явно, а не через super, хотя в данном случае это не очень принципиально). Соответственно IssueObjectWithInterface унаследован уже не от IssueClass, а от ProxyToIssueClass (см. схему)
Теперь рассмотрим подробнее устройство IssueClassWithInterface. ObjectWithInterface содержит в себе только один интерфейс, который и используется для доступа к атрибутам класса. В IssueClassWithInterface был разработан механизм для хранения и использования нескольких интерфейсов. При создании объекта IssueClassWithInterface (в переопределенном методе __new__) создается так называемый корневой интерфейс, который обеспечивает доступ только к методам класса IssueClass и методам самого класса IssueClassWithInterface. При добавлении нового интерфейса этот интерфейс автоматически наследуется от корневого интерфейса, если не указана специальная опция, пользоваться которой стоит только в специальных случаях, например, когда необходимо заблокировать доступ либо ко всем, либо к большинству методов классов IssueClass и ObjectWithInterface.
В Приложении 1 можно ознакомиться с краткой документацией класса ObjectWithOnterface.
Опишем особенности использования некоторых методов.
Метод filterInterfaceAyttrs рекомендуется использовать для получения атрибутов интерфейса определяющих тип заявки. Для этого keyword должен быть указан, как 'issue', а свойство predicate у соответствующего атрибута интерфейса должно быть выставлено в is_issue_predicate (подчеркну, что is_issue_predicate это не строка, а функция).
Тоже относится и к методу dump_interface наличие которого вызвано текущей архитектурой Roundup, о чем будет пояснено ниже.
Таким образом мы имеем возможность в динамике менять интерфейсы доступа к нашему мета-типу заявки определяя через интерфейс ее конкретный тип. Более того, как видно из предложенного решения интерфейсы могут быть указаны не только заранее, но и генерироваться динамически. Давление нового типа заявки из уже имеющихся в мета-типа полей не требует переинициализации базы данных, что существенно облегчает работу с системой. Так же используя методы получения всех полей определяющих тип заявки можно написать шаблон произвольного типа заявки, отображение всех типов заявки теперь становится автоматизированной операцией, ибо как видно из архитектуры достаточно взять все сохраненные в объекте мета-типа интерфейсы.
При этом подходе, однако, возникает проблема сохранения используемого для данной заявки интерфейса, то есть определения его типа. К сожалению backend к БД в Roundup не может сохранять произвольные объекты в динамике. В данной пилотной версии продукта было предложено решение, которое просто сохраняет поля определяющие тип заявки в специальный атрибут заявки. На данном этапе это решение оправдано своей простотой, а так же тем, что, скорее всего при просмотре уже сохраненной заявки вряд ли потребуется как-то менять ее тип (надо признать такое желание более чем странным). Соответственно при сохранении заявки в базу данных вызывается метод dump_interface и совершающий описанную выше операцию.
В будущем есть несколько решений данной проблемы. Например, можно сохранять dict (или dict'ы если интерфейс был от кого-то унаследован) описывающий данный интерфейс, по которому затем можно будет снова создавать интерфейс. Сложность реализации данного решения сопоставимо со сложностью уже имеющегося, однако его целесообразность пока не очевидна. Так же можно изменить backend Roundup на систему способную в динамике сохранять произвольные объекты языка Python. Надо сказать, что архитектурные решения в рамках проекта библиотеке Pli уже есть, однако написанный код еще не прошел стадию тестирования. Да и подобная задача может столкнуться с немалыми проблемами из-за архитектуры самого Roundup. Более того, необходимость такого решения не кажется целесообразным, так как вообще говоря в устоявшейся системе набор типов заявок изменяются крайне редко.
3.6.4 Информационная безопасность системы.
Поскольку создаваемая система рассчитана на достаточно массовое использование, и в том числе будет доступна из Интернет, особое внимание следует удалять информационной безопасности системы. Прежде всего, это касается авторизации и разграничения прав пользователей. В roundup есть достаточно мощная встроенная система авторизации и безопасности на основе ролей, её и предполагается использовать. Однако она будет иметь отношение лишь к «привилегированным» ролям в системе – операторам, исполнителям и администраторам. Конечные пользователи, согласно требованию простоты интерфейса и в виду отсутствия необходимости в авторизации будут пользоваться системой без неё. Вместо этого создаваемая система будет подразумевать возможность интеграции с различными системами защиты от спама. Такая интеграция будет произведена после первых признаков проблем в виде спама или флуда. Могут быть использованы различные средства, например ip black lists и/или human-test (просьба пользователя ввести число которое он видит на зашумлённой картинке для исключения использования системы роботами). Планируется отслеживать бюллетени информационной безопасности на предмет обнаружения возможных уязвимостей roundup. Кроме того, планируется инсталляция серверной части системы на машину, не несущую помимо этого иных критически важных функций.
4.1 Выводы по организации работы.
Участники проекта приобрели ценный опыт и поняли, насколько на самом деле важна организация эффективного внутрикоммандного взаимодействия, своевременный обмен информацией и постоянный контроль руководителем проекта сроков выполнения поставленных задач (последнее – особенно важно в условиях недостаточной мотивированности участнегов проекта). Были на практике опробованы несколько стратегий организации работы.
4.2 Выводы по технической части.
В результате была разработана система для поддержки работы с заявками разного типа, позволяющая разрабатывать шаблоны не для каждой операции каждого типа в отдельности, а только для каждой операции, работающей с абстрактным типом. Подобное решение не только существенно уменьшает работу разработчика шаблонов, но и делает систему более гибкой, позволяя теперь добавлять в нее заявки нового типа, внося минимальные изменения, сводящиеся к описанию самого типа.
4.3 Текущее состояние проекта.
В настоящий момент продолжается разработка пилотного этапа системы (разработка минимально необходимой функциональности). Полностью изучена технологическая платформа, решены все видные на данный момент нетривиальные технические вопросы. В целом налажено эффективное внутрикомандное взаимодействие. Однако делать выводы об успешности системы по части её непосредственного предназначения пока рано. В будущем планируется продолжить разработку системы, доведя её до успешного завершения.
Приложение 1 – интерфейс “IssueClassWithInterface”
IssueClassWithInterface предоставляет (исключая внутренние служебные методы) следующие методы:
addInterface(self, name, interface_class, **flags):
Этот метод добавляет новый интерфейс с именем name в объект.
Флаги:
inherit По умолчанию True. Добавляемый интерфейс наследуется от корневого интерфейса (CoreInterface).
select По умолчанию False. Добавляемый интерфейс делается активным.
selectInterface(self, name):
Этот метод делает активным интерфейс с именем name сохраненный в объекте.
addDictInterface(self, inter_name, format, **flags):
Этот метод создает интерфейс по описывающему его dict и добавляет его в объект под именем name.
Флаги аналогичны флагам в методе addInterface.
addObjectInterface(obj, inter_name, **flags):
Этот метод создает интерфейс по указанному объекту obj и сохраняет его в объект под именем name.
Флаги аналогичны флагам в методе addInterface.
filterInterfaceAttrs(self, keyword=None, interface_name=None):
Этот метод возвращает все атрибуты интерфейса сохраненного в объекте под именем interface_name с keyword. Если interface_name не указан берется интерфейс активный в данный момент интерфейс.