Лёгкое программирование: канбан-доска для GitLab за один рабочий день

    Рабочий понедельник начался со следующего диалога:

    Руководитель (P): У тебя в команде не понятно, кто чем занимается.
    Я (Я): Это да, у нас нет инструмента, который бы отображал общую картину работы над задачами. В гитлабе есть канбан-доски, но они только в контексте проектов и групп. Общая канбан-доска решила бы проблему.
    Р: Тогда сделай доску.
    Я: К утру будет готово.

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

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

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


    Перед тем как приступить к описанию технической части истории, расскажу про то, как мы в Онланте используем гитлаб. Группы в гитлабе у нас соответствуют внутреннему/внешнему заказчику или отдельному большому проекту, а проект — это репозиторий для кода конкретного сервиса. Например, у нас есть группа панели управления биллингом, в которой четыре проекта — сервисы и веб-приложения, а есть группа маркетинга, в которой находится проект для корпоративного сайта, микросервис для интеграции с amoCRM, всякие лендинги и прочее. Активных групп на текущий момент у нас девять штук, а программистов меньше, поэтому все прогеры участвуют во всех группах.

    И нам всего-то нужна была страница со всеми задачами из гитлаба, со всех групп и проектов, но такого функционала в GitLab, к величайшему сожалению, нет. Быстрый гуглёж не помог найти standalone-решение, поэтому

    Нам понадобится:


    — бэкэнд на Node.js — для сбора задач с GitLab и передачи их клиенту;
    — клиент на Vue.js — чтобы сделать быстро и красиво;
    — приправим всё это TypeScript, конечно!
    — PostgreSQL — там, где мы будем хранить информацию о задачах и их позицию на доске;
    — 8 часов рабочего времени;
    — хорошее настроение.

    Самое главное условие — разработка должна даваться легко и приносить удовольствие, назовём это «лёгким программированием».

    Зоопарк


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


    karcass


    За годы разработки у меня выработалось своё видение того, как должно строиться backend-приложение на Node.js, и этот подход оформился в виде каркаса — набора файлов. Для создания нового проекта я просто копировал директорию с шаблоном. Это не могло продолжаться вечно, и я сделал npm-пакет karcass (имя carcass уже было занято таким же энтузиастом-велосипедостроителем, как и я), который автоматизирует процесс создания основы для приложения:


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

    TypeORM


    «Я вижу какие-то сущности...»
    — из обсуждений на форуме экстрасенсов

    Поскольку нам нужно представить данные из гитлаба в удобном для нас виде, то в процессе между сбором этих данных и представлением, их нужно как-то хранить. Недавно я открыл для себя удивительную для экосистемы JS по своей красоте библиотеку для работы с БД — TypeORM. Она ещё зелёная, но имеет все шансы потеснить Sequelize с престола.

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

    import { Entity, PrimaryColumn, Column } from 'typeorm'
    
    @Entity({ name: 'group' })
    export class Group {
      @PrimaryColumn('integer')
      public id!: number
    
      @Column('varchar')
      public name!: string
    
      @Column('varchar')
      public url!: string
    }

    А для создания миграций я опять сделал велосипед помощника CreateMigrationCommand. В использовании эта команда очень проста:


    В классе Issue кроме полей, хранящих данные из Gitlab, мы добавляем поля из-за которых весь сыр-бор и начался, то есть, указывающие на положение задачи на канбан-доске:

    export class Issue extends AbstractEntity {
      /* ... */
      @Column('varchar', { name: 'kanban_status' })
      public kanbanStatus?: 'new'|'planed'|'working'|'checking'|'done'
    
      @Column('int', { name: 'kanban_order' })
      public kanbanOrder?: number
    }

    Нам нужна информация


    Когда я только начал своё знакомство с GitLab, он мне показался простым (читай «ненавороченным») продуктом, но работая с ним каждый день, я понимал насколько я ошибался. Вот и в этот раз я был удивлён — оказывается у GitLab очень богатый API, который покрывает почти весь функционал, доступный через пользовательский интерфейс. Но для решения нашей задачи понадобятся всего четыре метода, названия которых говорят сами за себя: /users, /groups, /projects, /projects/:id/issues. За непосредственное взаимодействие с GitLab будет отвечать GitlabService, а остальные классы будут к нему обращаться. Например, так выглядит имплементация метода updateGroups у класса GroupService:

    export class GroupService extends AbstractService {
      /* ... */
      public async updateGroups() {
        for (const data of await this.app.gitlabService.getGroups()) { // <= тут мы обращаемся к GitlabService
          let group = await this.getGroup(data.id)
          if (!group) {
            group = this.groupRepository.create({
              id: data.id,
            })
          }
          group.name = data.name
          group.url = data.web_url
          await this.groupRepository.save(group)
        }
      }
      /* ... */
    }

    За операцию получения сведений из гитлаба и обновления соответствующей информации в нашей БД будет отвечать команда UpdateProjectsCommand, которую можно вызвать из консоли, но наше приложение будет само её запускать с периодичностью, заданной в конфиге:

    export class Application {
      /* ... */
      protected initCron() {
        if (this.config.gitlab.updateInterval) {
          setInterval(async () => {
            if (!this.updateProjectsCommand) {
              this.updateProjectsCommand = new UpdateProjectsCommand(this)
            }
            await this.updateProjectsCommand.execute()
          }, this.config.gitlab.updateInterval * 1000)
        }
      }
      /* ... */
    }

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

    columns: [
      { key: 'new', title: 'Новые', color: 'rgb(255, 255, 219)' },
      { key: 'planed', title: 'Запланировано', color: 'rgb(236, 236, 191)' },
      { key: 'working', title: 'В работе', color: 'rgb(253, 214, 162)' },
      { key: 'checking', title: 'На проверке', color: 'rgb(162, 226, 253)' },
      { key: 'done', title: 'Выполнено', color: 'rgb(162, 253, 200)' },
    ],

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

    Для взаимодействия с фронтом нам нужен метод, который получает задачи из БД и сортирует их по «колонкам»:

    Показать IssueService.ts
    export class IssueService extends AbstractService {
      /* ... */
      public async getKanban() {
        let issues = await this.issueRepository.find({
          where: { updatedTimestamp: MoreThanOrEqual(new Date().getTimestamp() - 60 * 60 * 24 * 30) },
          order: { kanbanOrder: 'ASC', updatedTimestamp: 'DESC' },
        })
        const keys = this.app.config.columns.map(c => c.key)
        const result: { [key: string]: Issue[] } = {}
        for (let ki = keys.length - 1; ki >= 0; ki--) {
          const key = keys[ki]
          if (ki === keys.length - 1) { // Закрытые задачи автоматом помещаем в последнюю колонку
              result[key] = issues.filter(i => i.closed)
          } else if (ki === 0) { // Всё, что осталось, добавляем в первую колонку, чтобы не потерялось
              result[key] = issues
          } else {
              result[key] = issues.filter(i => i.kanbanStatus === key)
          }
          issues = issues.filter(i => !result[key].includes(i)) // Убираем уже отфильтрованные задачи
        }
        return result
      }
      /* ... */
    }

    И контроллер, который сохраняет новые позиции задач:

    Показать IssueController.ts
    export default class IssueController extends AbstractController {
      /* ... */
      public async kanbanUpdate(data: IQueryData) {
        const keys = this.app.config.columns.map(c => c.key)
        for (const c of data.params as { key: string, title: string, color: string, issues: number[] }[]) {
          if (keys.indexOf(c.key) < 0) {
            continue
          }
          let index = 0
          for (const i of c.issues) {
            index++
            const issue = await this.app.issueService.getIssue(i)
            if (!issue) {
                continue
            }
            issue.kanbanStatus = c.key
            issue.kanbanOrder = index
            this.app.issueService.issueRepository.save(issue)
          }
        }
      }
    }

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

    Vue CLI — вместо тысячи слов



    TSX


    Для облегчения рефакторинга, поиска ошибок, линтинга и прочего душевного спокойствия мы используем tsx-шаблоны, которые Vue.js поддерживает из коробки* почти. Вот, например, самый главный в нашей доске компонент, компонент представления задачи:

    Показать код Issue.tsx
    import { Vue, Component, Prop, Watch } from 'vue-property-decorator'
    import './Issue.css'
    import { CreateElement, VNode } from 'vue'
    
    interface IIssue {
      groupId: number
      groupName: string
      groupUrl: string
      projectName: string
      projectUrl: string
      url: string
      title: string
      executor: { color: string, name: string }
      spent: number
      estimate: number
    }
    
    @Component
    export default class extends Vue {
      @Prop()
      public issue!: IIssue
      public issueValue!: IIssue
      public selectUser = false
    
      @Watch('issue', { immediate: true })
      public onIssueChange() {
          this.issueValue = this.issue
      }
    
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
      public render(h: CreateElement): VNode {
        return <div class="kvcIssue">
          <div class="kvciTitle">
            { this.issueValue.groupId ? <small><a href={ this.issueValue.groupUrl } target="_blank">
                { this.issueValue.groupName }
            </a> / </small> : undefined }
            <a href={ this.issueValue.projectUrl } target="_blank">{ this.issueValue.projectName }</a>
          </div>
          <div class="kvciText"><a href={ this.issueValue.url } target="_blank">{ this.issueValue.title }</a></div>
          <div class={ ['kvciExecutor', this.issueValue.executor ? '' : 'none'] }>
            <span class="timeTd">
              <span title="Времени потрачено">{ this.issueValue.spent.time() }</span> / <span title="Времени планировалось">
                { this.issueValue.estimate.time() }</span>
            </span>
            { this.issueValue.executor ? <span class="kvcieUser" style={ { background: this.issueValue.executor.color } }>
              { this.issueValue.executor.name }
            </span> : <span class="kvcieSelect">не определён</span> }
          </div>
        </div>
      }
    }

    Этот компонент отображает плашку с задачей. Обратите внимание, что у задачи есть фактически потраченное и запланированное время:


    Ещё есть компоненты Column.tsx и Kanban.tsx. Всего три компонента обеспечивают представление задач на доске:


    За перемещение задач на доске отвечает Vue.Draggable, ноги которого растут из SortableJS. В использовании крайне прост:

    import Draggable from 'vuedraggable'
    /* ... */
    export default class extends Vue {
      /* ... */
      public render(h: CreateElement): VNode {
        /* ... */
        <Draggable class="kvcIssues" vModel={ this.issuesValue } group={ { name: 'issues', pull: true, put: true } }
            onEnd={ this.onDrag } onAdd={ this.onDrag }
        >
            { this.issuesValue.map(i => <Issue key={ i.id } issue={ i } />) }
        </Draggable>
        /* ... */
      }
    }

    Наверняка вы заметили, что у каждого пользователя свой цвет, который используется для отображения логина. Это очень удобно — среди кучи задач, можно быстро найти свои. Добиться «разноцветности» можно двумя путями: задать цвет руками для каждого пользователя или генерировать его на основе чего-то.

    Назови мне свой логин, и я скажу, какой у тебя цвет


    За генерацию цвета отвечает геттер color у класса User. Я специально реализовал этот функционал на бэкэнде, потому что мне понадобилась криптография, а тащить на фронт целую библиотеку ради такой мелкой задачи — преступление. Да и правильнее, когда за характеристики сущностей отвечает бэк, а не фронт:

    export class User extends AbstractEntity {
      /* ... */
      public get color() {
        const hash = crypto.createHash('md5').update(this.username).digest() // Старичка md5 не стоит раньше времени сбрасывать со счетов. Для некоторых задач он по-прежнему хорош и быстр.
        const result: number[] = []
        for (let i = 0; i < 3; i++) {
          result.push(Math.round(200 - hash[i] / 255 * 150)) // Фон должен быть не сильно ярким, но и не сильно тёмным, чтобы на нём читался логин пользователя
        }
        return `rgb(${result.join(', ')})`
      }
    }

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

    Вот и всё


    Рабочий вторник начался со следующего диалога:

    Р: Ну как доска, готова?..
    Я: Да, вот.
    Р: О, круто!

    Результат моего «трудового будня» лежит здесь: https://github.com/onlanta/kanban. Его можно не только посмотреть-потрогать, но и использовать (инструкции там же). А можно на его основе построить целый таймтрекер для гитлаба, что мы в компании и сделали.

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

    ГК ЛАНИТ
    550,18
    Ведущая многопрофильная группа ИТ-компаний в РФ
    Поделиться публикацией

    Комментарии 39

      +1

      Возможно я что-то проглядел, но в гитлабе есть встроенный функционал канбан-доски — Issues -> Boards.

        +4

        Верно, но там канбан-доски только в контексте проекта или группы, а не всего гитлаба.

          +1

          Делают уже и для мульти-проектов: https://gitlab.com/gitlab-org/gitlab-foss/issues/53811

            0

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

              0

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

                0

                Если бы вносить изменения в OpenSource было бы чересчур долго и сложно, то всё движение уже загнулось бы. А мы напротив, вовсю наблюдаем его расцвет.

                  +2

                  ОпенСурс очень неравномерный. И у него тоже бывает и человеческий фактор, и стандарты качества, и какой-то план развития. Поэтому в зависимости от проекта пропихнуть нужный фикс или фичу бывает от "просто" до "невозможно"


                  Да, можно всегда форкнуться, но это ужасно, т.к. придется потом таскать и обновлять свой набор патчей. А это прям… непросто.

          0
          А вот kanboard с плагинами разве не делает то же самое?
          +7

          Я бы мог обстоятельно рассказать почему мой велосипед в данном случае лучше и его непременно надо было сделать, но с канбордом всё оказалось просто: когда я понял, что в каждом проекте гитлаба отдельно нужно будет добавлять вебхук, а в канборде auto actions не позволяют прикурить уже существующие в гитлабе задачи, я психанул и забил на канборд :-) В дальнейшем это оказалось правильным решением — синхронизировать из гитлаба потраченное и запланированное на задачи время канборд не умеет, а доска у нас выросла в таймтрекер.

            +5
            Я думал, это я такой испорченный, что слово carcass для меня означает совсем не то, что имел в виду автор… но нет: www.google.com/search?q=carcass

            Сорри за оффтоп, не сдержался)))
              +13

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

                +2

                Ну или так )) Меня эта мысль почему-то не посетила...

                  +1
                  Я помню была тут статья, где автор героически пилил расширение к Visual Studio чтобы показывать при компиляции ошибки сгруппированные по проектам, а потом оказалось, что это включается двумя кликами в дефолтном интерфейсе студии.
                    0
                    Это прям история создания половины велосипедопроектов
                      0

                      Эх, ещё бы ссылку запостили, интересно читнуть )

                +1
                Ну или использованием сервисов вроде Jira. Но в нашей команде один разработчик на удалёнке, а гитлаб в закрытом контуре (недоступен из интернетов по соображениям информационной

                1. И как Ваше решение вопрос закрытого контура решило?
                2. ВПН прокинуть к джире тоже нельзя было реализовать?
                  +1
                  1. Канбан-доска тоже в закрытом контуре, если точнее, на той же виртуалке, что и гитлаб;
                  2. Это вопрос, касающийся некоторых аспектов взаимодействия с нашей службой информационной безопасности. В целом, могу сказать, что всё очень сложно ;-)
                  3. Но даже если решить этот вопрос, Jira ещё и по деньгам недёшево нам обошлась бы. Разработчиков мало (пока, надеюсь), но вот менеджеров, следящих за тасками, хватает, поэтому standalone-лицензия, боюсь, обошлась бы нам в $2.5к в год, что для небольшой команды как-то дороговато.
                    +2
                    Странные вы какие-то. Разработчики удалённые у вас есть, т.е. доступ к коду какой-то удалённый имеется. При этом доступ к списку задач организовать мешает какая-то безопасность.
                  0
                  — 8 часов рабочего времени;

                  Круто. Я почему-то уверен, что если заказывать такой функционал на фрилансе, исполнитель зарядил бы цену минимум в 500$ и срок минимум неделя.
                    +2

                    4 дня на погружение, обсуждение, тз, согласование, правки. 8 часов на реализацию))


                    $500 на 40 часов рабочей недели, не так и много

                      0
                      У меня комплекс неполноценности развивается, если честно.
                      Так-то ничего сложного сделать за день можно наверно… Но покопаться в API gitlaba, покопаться с авторизацией и гитлаба и внутренней, поискать нужную библиотечку для отображения канбана(С учетом того. что обычно дай бог третья подходит, а на каждую по часику нужно), разобраться с ней, пересобачить формат гитлабовских данных в формат библиотеки канбана, потрахаться с css. Обязательно ж какой-нить затык будет, задеплоить это все(особенно, когда чувак описал сложности с безопасностью). Если все гладко проскочит, то легко но ведь так не бывает((
                        0

                        Бывают моменты озарения, особенно когда ловишь фан. Комплексовать не стоит, я далеко не всегда так работаю и над простейшими задачами, не требующими ни разбора API, ни подбора компонентов вполне могу день тупить )

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

                      Vpn не? Иначе как он работает? Перегибы в ИБ ведут к глупостям. Что разработка ненужных штук, что создание странных кадавров для обхода ИБ (типа туннель через туннель через https)


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

                      Шутка зашла

                        0
                        Перегибы в ИБ ведут к глупостям. Что разработка ненужных штук, что создание странных кадавров для обхода ИБ (типа туннель через туннель через https)

                        Сказать нечего, могу только плюсануть )

                        +1
                        Из всех досок магнитные были повеселее, отрывали на полчаса от экрана и добавляли живого общения. Роль руки робота для удалённых команд выполнял человек Р, часто по собственному желанию. Это помогает не отстраняться от сути проекта, а для многих выполняет роль полезной работы.

                        … что его команде нужна канбан-доска

                        Все возможные доски, которые я видел, нужны были именно человеку Р, а не команде.
                        Часто и к сожалению для скрытия неквалификации в области менеджмента проектов.
                        Все талантливые Р, которых я видел у таких досок, использовали её именно как возможность лично обсудить и понять возникающие сложности.

                        А в целом, простой компонент получился, решающий свою задачу.
                          +1

                          Вы правы. Линейному разработчику в команде просто нужен пул задач, а как они распределяются между участниками — не его головная боль. Поэтому доска нужна, в первую очередь, организатору разработки. Есть различные техники вовлечь разработчиков в игру с доской, но сути это не меняет.


                          И да, физическая доска с бумажными стикерами, конечно, приятнее.

                          +1
                          А чем Trello не зашло?
                          Ведь то, что у вас в итоге получилось ничем не отличается от trello с интеграцией gitlab.
                            +2

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

                              +1

                              Ну, как бы есть разница между "наш гитлаб из интернета недоступен" и "наш гитлаб из интернета недоступен, но при этом свободно может ходить в интернет". Прошу прощения за уточнение. Мы, например, столкнулись с тем, что куча функционала — то же сканирование образов, docker executor etc. без плясок с бубнами БЕЗ доступа в интернет (хотя бы на скачивание) не заводится. Это реально очень интересная тема — как в нынешних условиях в закрытом контуре запускать такие штуки, так чтобы было безопасно и не очень больно.

                                +2
                                наш гитлаб из интернета недоступен, но при этом свободно может ходить в интернет

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


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


                                Тема действительно интересная, вы верно заметили.

                            +1

                            ShibaOn, а почему у вас в вакансии с названием Разработчик BI, описан функционал 1С-ника?

                              +2

                              Рабочая среда началась со следующего диалога:


                              Я (Я): У нас тут в комментах спрашивают почему в вакансии BI описание для 1С-ника?
                              Главная по корпоративному блогу ЛАНИТа (ГКБЛ): Сейчас поправим, а ты отшутись как-нибудь.

                              +1
                              Честно говоря, если кто-то из руководства говорит:
                              «У тебя в команде не понятно, кто чем занимается.»

                              То:
                              1) Чем занимается тимлид? Почему он базовые свои обязанности не выполняет?
                              2) За такие велосипедные велосипеды для решения базовой задачи я бы тимлида разжаловал в рядовые, ведь видно, что кодить ему нравится больше, чем управлять, а основная задача у тимлида — управлять командой.
                                0
                                ровно такие же вопросы, почему руководителя свыше интересует кто чем занимается? Есть тим лид, это интерфейс для работы с командой, если присуствует ручное управление, скорее всего в консерватории что-то надо менять
                                  0
                                  Вы не поняли. Руководителя интересует, почему тимлид не имеет этой информации. С его стороны совершенно оправданна такая озабоченность.
                                  +3

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

                                    +3
                                    Это пророческий коммент!

                                    Это опыт, дружище, глаз наметан на такие ситуации )

                                    Вопрос о том, куда расти сеньору

                                    Выбор есть, вопрос в том, какие возможности предоставляет Вам текущее место работы:
                                    1) Technical Lead — главный по технологиям в команде. Кодить нужно много, но нужен широкий кругозор для выбора технологий и педантичность в их отборе.
                                    2) Architect — главный по архитектуре, но тут кодить нужно сильно меньше.
                                    3) Остаться в сеньорах и кодить в удовольствие. Так же можно замещать тимлида по каким-то вопросам или полностью в его временное отсутствие(отпуск).
                                    4) DevOps. Тут открывается непочатый край интеграций с сервисами и тонкого тюнинга.

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

                                Самое читаемое