Как спроектировать код приложения с помощью UML: практический пример для системы электронного документооборота. Диаграммы вариантов использования, классов, состояний и последовательности.
Определение возможностей и статической структуры системы с UML
Разумеется, современные системы электронного документооборота (СЭД) имеют множество функций, которые касаются не только работы с документами, но и организации командного взаимодействия. Однако, чтобы короткая статья не превратилась в книгу, рассмотрим только самые базовые возможности типовой СЭД: создание документа и его согласование. Создать документ может любой пользователь.
Документ может включать в себя файл в формате pdf, doc, ppt, xls. Чтобы документ был доступен для просмотра другим пользователям, автор документа, создавший его локально, должен его опубликовать. Далее инициатор процесса согласования отправляет его по маршруту согласования (workflow). Маршрут согласования состоит из задач, каждая из которых имеет исполнителя, а также плановые и фактические сроки выполнения. Исполнитель задачи согласования, т.е пользователь, выполняющий поставленную задачу, может согласовать документ или отказать в согласовании.
Чтобы показать вышеописанную постановку с помощью UML, начнем с диаграммы вариантов или сценариев использования (Use Case), которая демонстрирует, какие возможности система предоставляет пользователям (акторам). Как обычно, для удобства сопровождения, буду использовать мой любимый PlantUML – движок, который позволяет описать диаграмму кодом и визуализирует ее в виде картинки.
Скрипт PlantUML для этой диаграммы:
@startuml title: Управление документами actor Пользователь as U actor Инициатор as I actor Исполнитель as E E -|> U U <|- I rectangle СЭД { U --> (Создать документ) I --> (Отправить документ на согласование) E --> (Выполнить задачу согласования) (Создать документ) <.. (Опубликовать документ) : extend (Отправить документ на согласование) .> (Создать маршрут согласования) : include (Создать маршрут согласования) .> (Добавить задачу согласования) : include (Отправить документ на согласование) ..> (Опубликовать документ) : include (Выполнить задачу согласования) <.. (Согласовать) : extend (Выполнить задачу согласования) <.. (Отказать) : extend } @enduml
Далее опишем модель данных системы, которая будет реализована в коде приложения. Подчеркну, что здесь не идет речь об уровне хранения, т.е. таблицах реляционной базы данных или схемы документно-ориентированного хранилища типа MongoDB или Elasticsearch. UML-диаграмма классов описывает набор классов – программных конструкций, которыми оперирует разработчик в коде приложения, а не структуры хранения данных в базе.
Чтобы реализовать возможность работы пользователей с документами, маршрутами и задачами, определим 5 классов:
- user – пользователь с атрибутами логин login, адрес электронной почты (email), номер телефона (phone), роль (role), например, секретарь, администратор, сотрудник;
- document – документ с атрибутами номер (number), дата и время создания (created_timestamp), файл вложения (file), автор (author), статус (status), например, новый, опубликован, на согласовании, согласован, несогласован;
- file – файл, присоединенный к документу с атрибутами формат файла (format), например, pdf, doc, xls, ppt и размер файла (size);
- route – маршрут движения документа с атрибутами дата и время создания (created_timestamp), дата и время старта выполнения (started_timestamp), дата и время завершения выполнения (finished_timestamp), документ, связанный с маршрутом (document), инициатор маршрута (initiator), список задач в рамках маршрута (tasks), статус маршрута (status), например, новый, начат, выполняется, отменен, завершен;
- task – задача с атрибутами название (name), дата и время создания (created_timestamp), дата и время старта выполнения (started_timestamp), дата и время завершения выполнения (finished_timestamp),исполнитель (executor), статус задачи (status), например, новая, поставлена, принята к исполнению, отклонена исполнителем, выполняется, отменена инициатором, согласована исполнителем, несогласована исполнителем, заморожена, принята инициатором, завершена.
Применяя термины домено-ориентированного проектирования (DDD, Domain-Driven Design) к этим классам, отметим, что маршрут согласования документа (route) будет агрегатом, т.к. включает в себя поля, указывающие на пользователя-инициатора (initiator), документ (document), набор задач (tasks). Аналогично, класс задача (task) будет агрегатом для пользователя-исполнителя (executor), а класс документ будет агрегатом для файла (doc_file). Поэтому ромбик будет на стороне классов-агрегатов. Все связи, кроме маршрут-задача будут агрегациями, т.к. связи между этими классами являются достаточно сильными и выражают отношение часть-целое, но жизненный цикл их объектов не одинаков. А классы маршрут-задача имеет смысл связать через композицию, т.к. уничтожение объекта класса route предполагает уничтожение входящих в него задач, т.е. объектов класса task.
Поскольку статусы жизненного цикла объектов класса route, task и document выбираются из ограниченного перечня, определим эти наборы значений как перечисления. В терминах DDD это будут объекты-значения. Свяжем эти перечисления связью направленной агрегации с агрегатами и сущностями, т.е. классами документ, маршрут и задача. Таким образом, UML-диаграмма классов для рассматриваемого примера будет выглядеть так.
Скрипт PlantUML для этой диаграммы:
@startuml skinparam linetype ortho class user { - login: str - email: str - phone: str - role: Role + info() } class document{ - number: str - created_timestamp: datetime - file: doc_file - author: user - status: document_status + add_file(doc_file: doc_file) + delete_file() + publish_document() + review_document() + approve_document() + reject_document() + info() } class doc_file{ - format: FileFormat - size: int } class task{ - name: str - created_timestamp: datetime - started_timestamp: datetime - finished_timestamp: datetime - executor: user - status: task_status + start_task() + receive_task() + decline_task() + cancel_task() + perform_task() + accept_task() + complete_task() + reject_task() + freeze_task() + finish_task() + update_task_status() + info() } class route{ - created_timestamp: datetime - started_timestamp: datetime - finished_timestamp: datetime - document: document - initiator: user - tasks: List[task] - status: route_status + add_task(task: task) + delete_task(task: task) + start_route() + cancel_route() + perform_route() + complete_route() + get_all_tasks() + update_route_status() + info() } enum task_status { + new = 'новая' + assigned = 'поставлена' + started = 'принята к исполнению' + declined_by_executor = 'отклонена исполнителем' + performing = 'выполняется' + canceled_by_initiator = 'отменена инициатором' + completed_by_executor = 'согласована исполнителем' + rejected_by_executor = 'несогласована исполнителем' + frozen = 'заморожена' + accepted_by_initiator = 'принята инициатором' + finished = 'завершена' } enum route_status { + new = 'новый' + started = 'начат' + in_progress = 'выполняется' + canceled = 'отменен' + completed = 'завершен' } enum role { + secretary = 'секретарь' + administrator = 'администратор' + employee = 'сотрудник' } enum file_format { + pdf = 'pdf' + doc = 'doc' + xls = 'xls' + ppt = 'ppt' } enum document_status{ + new = 'новый' + published = 'опубликован' + on_review = 'на согласовании' + approved = 'согласован' + rejected = 'несогласован' } user "1..*" o--> "1" role task_status "1" <--o "1..*" task route_status "1" <--o "1..*" route route "1" *-> "1..*" task route "1..*" o--> "1" document document "1..*" o--> "1" user route "1..*" o--> "1" user doc_file "0..1" <-o "1" document file_format "1" <--o "1..*" doc_file task "1..*" o--> "1"user document "1..*" o--> "1" document_status @enduml
Закончив со статической структурой проектируемой системы, далее опишем ее поведение с помощью диаграмм состояний и последовательности.
DDD, ООП и UML для аналитика
Код курса
BUML
Ближайшая дата курса
9 декабря, 2024
Продолжительность
16 ак.часов
Стоимость обучения
36 000 руб.
Проектирование поведения системы: диаграммы состояний и последовательности
Работа с документом в СЭД предполагает изменение его состояния. Описать жизненный цикл объекта любого класса в UML можно с помощью диаграммы состояний (state machine).
Начнем с документа. В СЭД жизненный цикл документа включает создание, публикацию, возможное согласование или отклонение, и завершение. Каждый переход между состояниями осуществляется через вызов соответствующей функции. После создания документ находится в состоянии новый (new), когда он только что создан и еще не опубликован. После публикации документ переходит в состояние опубликован (published) и становится доступен для просмотра и дальнейшего согласования. Будучи отправленным по маршруту согласования, документ переходит в состояние на согласовании (on_review). Если документ прошел согласование и одобрен, он переходит в состояние согласован (approved). Иначе, когда документ не прошел согласование и был отклонен, он переходит в состояние несогласован (rejected). Документ завершает свой жизненный цикл, когда он либо согласован, либо несогласован. UML-диаграмма состояний для класса document будет выглядеть так.
Скрипт PlantUML для этой диаграммы:
@startuml title Диаграмма состояний для документа - класс document [*] --> new new : новый new --> published : publish_document() published : опубликован published --> on_review : review_document() on_review : на согласовании on_review--> approved : approve_document() on_review --> rejected : reject_document() approved: согласован rejected: несогласован approved --> [*] rejected --> [*] @enduml
Жизненный цикл маршрута согласования документа начинается с создания нового маршрута, затем он может быть начат, выполняться, завершиться или быть отменен. Когда создается новый маршрут, он находится в состоянии новый (new). Когда маршрут запущен, он переходит в состояние начат (started), а потом выполняется (in_progress), когда выполняются его задачи. Когда все задачи маршрута выполнены, он успешно завершен (completed) и это его конечное состояние. Если маршрут отменен в процессе выполнения, он считается отмененным (canceled), что тоже является конечным состоянием. UML-диаграмма состояний для класса route будет выглядеть так.
Скрипт PlantUML для этой диаграммы:
@startuml title Диаграмма состояний для маршрута - класс route [*] --> new new : новый new --> started : start_route() started : начат started --> in_progress : perform_route() in_progress : выполняется in_progress --> completed : complete_route() in_progress --> canceled : cancel_route() canceled: отменен completed: завершен completed --> [*] canceled --> [*] @enduml
Для задачи (класс task) жизненный цикл выглядит так: после создания задача находится в состоянии новая (new). Когда она назначена на исполнителя, задача переходит в состояние назначенная (assigned). После того, как исполнитель начал работу над ней, задача становится начатой (started). Если исполнитель отказался выполнять задачу, она будет отклонена исполнителем (declined_by_executor). Если инициатор решил отменить задачу, она будет отменена инициатором (canceled_by_initiator). Пока исполнитель активно работает над задачей, она выполняется (performing). Если исполнитель задачи согласования согласовал документ, задача переходит в статус согласована исполнителем (completed_by_executor). Если исполнитель задачи согласования не согласовал документ, выполняемая задача переходит в статус несогласована исполнителем (rejected_by_executor). Выполняемая временно приостановленная задача переходит в статус заморожена (frozen), откуда ее выполнение может быть возобновлено или отменено (canceled), если инициатор отменил задачу или маршрут. Когда инициатор маршрута, в рамках которого исполнителю была поставлена задача, принял результаты ее выполнения (согласована или не согласована), она становится принятой (accepted_by_initiator). После принятия инициатором задача становится завершенной (finished) и это ее конечное состояние. UML-диаграмма состояний для класса task будет выглядеть так.
Скрипт PlantUML для этой диаграммы:
@startuml title Диаграмма состояний для задачи - класс task [*] --> new new : новая assigned : поставлена started : принята к исполнению declined_by_executor : отклонена исполнителем canceled_by_initiator : отменена инициатором completed_by_executor : согласована исполнителем frozen : заморожена performing : выполняется accepted_by_initiator : принята инициатором finished : завершена new --> assigned : start_task() assigned --> started : receive_task() started --> declined_by_executor : decline_task() started --> performing : perform_task() performing --> completed_by_executor : complete_task() rejected_by_executor : несогласована исполнителем performing --> rejected_by_executor : reject_task() performing --> frozen: freeze_task() frozen --> performing: perform_task() frozen --> canceled_by_initiator : cancel_task() canceled_by_initiator --> finished : finish_task() completed_by_executor --> accepted_by_initiator : accept_task() rejected_by_executor --> accepted_by_initiator : accept_task() declined_by_executor --> accepted_by_initiator : accept_task() accepted_by_initiator --> assigned : start_task() accepted_by_initiator --> canceled_by_initiator : cancel_task() accepted_by_initiator --> finished : finish_task() finished --> [*] @enduml
Поскольку согласование документа предполагает изменение его состояния вследствие выполнения задач согласования, опишем эту логику на UML-диаграмме последовательности. Инициатор создает и управляет задачами внутри маршрута согласования документа, при этом документ проходит этапы публикации, ревизии и утверждения, а исполнитель взаимодействует с задачами и документом в процессе согласования. Инициатор создаёт документ и отправляет его по новому маршруту согласования, а также добавляя задачи в маршрут. После добавления всех задач, по каждой из которых установлен исполнитель, инициатор запускает маршрут. При этом документ публикуется, если ранее он не был опубликован. После начала выполнения задачи статус документа становится на согласовании. Если исполнитель принял задачу и начал её выполнять, статус задачи меняется на выполняется, а также обновляется статус маршрута — он тоже становится выполняемым. Если исполнитель не согласовал документ, меняется статус задачи и всего документа, поскольку документ может быть согласованным только при согласовании всех лиц. Когда все задачи завершены, у каждой из них есть время завершения, маршрут тоже завершается. Если все задачи в маршруте согласованы, документ считается согласованным.
Детализация варианта использования Согласовать документ на UML-sequence выглядит так. Сложная бизнес-логика изменения состояний связанных объектов показана с помощью фреймов.
Скрипт PlantUML для этой диаграммы:
@startuml title Согласование документа actor initiator as I participant route as R participant task as T participant document as D actor executor as E I -> R : create(document, initiator) activate R R -> R: set created_timestamp = datetime.now() R -> R: set status = route_status.new R --> I : route.info deactivate R I -> R : add_task(task) activate R R -> T : info() activate T T --> R : task.info deactivate T R -> R : tasks.append(task) R --> I : route.info deactivate R I -> R : start_route() activate R R -> R : set started_timestamp() = datetime.now() R -> R : set status = route_status.started R -> D : publish_document() activate D D -> D : set status = document_status.published D --> R : route.info deactivate D R -> T : start_task() activate T T -> T : set status = task_status.assigned T --> R : task.info deactivate T R -> D : review_document() activate D D -> D : set status = document_status.on_review D --> R : document.info deactivate D E -> T : receive_task() activate T T -> T : set status = task_status.started T --> E : task.info alt исполнитель принял задачу E -> T : perform_task() T -> T : set status = task_status.performing T -> R : update_route_status(task.status) R -> R : set status = route_status.in_progress alt исполнитель согласовал документ E -> T : complete_task() T -> T : set status = task_status.completed_by_executor T --> E : task.info T -> R : update_route_status(task.status) else исполнитель не согласовал документ E -> T : reject_task() T -> T : set status = task_status.rejected_by_executor T --> E : task.info T -> R : update_route_status(task.status) deactivate T R -> D : reject_document() activate D D -> D : set status = document_status.rejected D --> R : document.info deactivate D end alt I -> T : accept_task() activate T T -> T : set status = task_status.accepted_by_initiator T -> T : finish_task() T -> T : set status = task_status.finished T -> T : set finished_timestamp = datetime.now() T --> I : task.info deactivate T else исполнитель отклонил задачу E -> T : decline_task() activate T T -> T : set status = task_status.declined_by_executor T -> R : update_route_status(task.status) deactivate T end alt opt инициатор отменил задачу I -> T : cancel_task() activate T T -> T : set status = task_status.canceled_by_initiator T -> T : set finished_timestamp = datetime.now() T -> R : update_route_status(task.status) T --> I : task.info deactivate T end opt group update_route_status(task.status) alt у всех задач есть finished_timestamp T -> R:complete_route() activate T R -> R : set status = route_status.compeleted R -> R : set finished_timestamp = datetime.now() R --> T : route.info deactivate T alt все задачи в этом маршруте согласованы, имеют статус completed_by_executor R -> D : approve_document() activate D D -> D : set status = document_status.approved D --> R : document.info deactivate D end alt end alt end group R --> I : route.info deactivate R @enduml
Таким образом, с помощью нескольких видов UML-диаграмм можно довольно детально спроектировать статическую структуру и динамику поведения информационной системы, чтобы потом реализовать ее в коде. Как это сделать, я покажу в следующей статье.
DDD, ООП и UML для аналитика
Код курса
BUML
Ближайшая дата курса
9 декабря, 2024
Продолжительность
16 ак.часов
Стоимость обучения
36 000 руб.
Узнайте больше про UML-диаграммы и инструменты их разработки на моих курсах в Школе прикладного бизнес-анализа на базе нашего лицензированного учебного центра обучения и повышения квалификации системных и бизнес-аналитиков в Москве: