Дима, привет!
Нет, не могу в двух словах описать. Слов и так много написано :) Важно не то, что можно будет мочь потом, а то, что можно мочь, умея работать со строками и массивами.
А что потом на выходе у меня получится - это совсем другой вопрос, там будет видно в процессе.
Николай, привет!
Правильно понял, что это по сути - интерфейс для управления образами докера?
Как один блок для создания "низкого".
Можешь в двух словах описать, что должно получиться в самом конце? Примерно, что программист на этом языке будет мочь?
Всем привет!
Сегодня хочу поделиться небольшим открытием (новая версия docker и плагины для него) и как я к нему пришел через свой новый pet-project. Вообще сложно назвать его проектом (потому что я постоянно что-то экспериментирую и часто это забрасывается). Но тем не менее, может что-то и вырастит.
Статья должна быть полезна для тех, кто хочет научиться именно программированию, так как я здесь расскажу про некоторые неочевидные, но базовые и полезные вещи, а так же дам пару полезных ссылок.
Мой новый проект - это эксперименты в области low-code. Вот пара заметок на хабре: раз, два и три. И хотя это направление вызывает у программистов больше негатива, чем позитива, я все же считаю, что это действительно перспективное направление. То есть сейчас это конечно же далеко от желаемого уровня и сложно применимо в реалиях, тем не менее сама идея имеет место быть. Все-таки при должном уровне проработки действительно часть бизнес-процессов можно перенести в эту область. Вот один из таких проектов с открытым кодом: https://n8n.io и видео оттуда:
Так может не все сразу понятно, но если коротко: специалист в интерфейсе набрасывает логику, накидывая специальные компоненты, конфигурируя их и устанавливая связи. На выходе что-то там происходит. То есть здесь не написали ни строчки кода, но какая-никакая, но логика выполняется. Это и есть low-code (буквально Мало кода. Еще есть термин zero-code (ноль кода), но судя по всему это одно и тоже).
Так чем же мне все это интересно и как это связано с программированием (если тут программировать и не надо)? На самом деле правильно говорят противники этой идеи: чтобы всем этим пользоваться, все равно надо уметь программировать, потому что не программисту реально многие вещи не понятны. Они не понимают типов данных, не всегда могут выстроить логические цепочки, не могут создать набор правильных переиспользуемых компонентов и т.д. и т.п. И я в этом с ними согласен. Но я считаю, что это как раз может быть неплохим материалом именно для изучения программирования, то есть формирования у себя этого правильного понимания и восприятия. То есть когда мы пытаемся что-то с этим сделать, мы испытываем недостаток базовых знаний. Соответственно, начинаем у себя эти знания прокачивать. И потом начинаем понимать, что такие-то знания полезны для тех или иных ситуаций. И сейчас я это покажу на своем личном примере.
Первое, что я захотел сделать, это разработать платформу по управлению докер-проектами. Здесь я сразу оговорюсь, что во-первых, я буду писать свою платформу с нуля, а не использовать чужую, а во-вторых, это конечно же будет очень ограниченное с функциональной точки зрения решение. Но на то это и эксперименты :)
Итак, логика предполагается такая:
1. За основу будет взята платформа @prisma-cms/nextjs-nexus
2. Писаться будет все соответственно на TypeScript/GraphQL/React и т.п. (то есть не планируется пока установка какого-либо дополнительно программного обеспечения, все штатными средствами).
Отдельно уточню, что выполняться будет на линуксе, так что если у вас другая операционная система, то что-то может.
3. Первая нода будет запрашивать и выводить список контейнеров в указанной директории.
Здесь сразу уточню, что Нода - это мною же введенный термин самостоятельной единицы интерфейса, выполняющей самостоятельную логику.
4. Список запущенных контейнеров проверяется на наличие в нем каждой службы из заданного списка. Если контейнера нет, то он должен быть запущен. Если запустить не удалось, то ошибка.
5. Если нигде не возникло ошибки, то успех.
Вот примерная логика:
И вот здесь сразу можно усмотреть несколько логических вопросов. Самый главный вопрос: как организовать логику так, чтобы конечный успех был только в случае успеха для всех контейнеров. То есть здесь нужен такой обработчик, который проверит, чтобы истинных результатов было столько же, сколько и проверок. Уточню в чем здесь такая закавырка: в коде мы просто обычно на месте сразу прописываем обработчики и тут же решаем что делать дальше. А в подобных системах логика чуть другая: здесь есть точка входа и точки выхода. То есть в текущей точке логики я должен отправить дальше полученную информационную единицу и там уже должно быть принято решение что и как. Чтобы было понятней, отследите логическую целочку для одного отдельного контейнера. Получается так: проверяем в списке контейнеров конкретный контейнер, если он запущен, то идем дальше на успех; если не запущен, то идем на ноду запуска контейнера; если получилось запустить, то дальше на конечный успех, а если не получилось, то на ошибку. И здесь вроде все понятно и логично. Но у нас произвольный список контейнеров. Если их будет два и больше, то какой-то контейнер проверка выполнится быстрее и придет на конечный Успех. И что делать в таком случае? Где гарантия, что другой контейнер тоже успешно проверится? Здесь сразу уместно упомянуть, что есть процессы последовательные, а есть параллельные (в JS на собеседованиях любят спрашивать про синхронный/асинхронный код).
В итоге получается, что даже еще до этапа написания конечного кода, уже можно сломать мозг о рисование логической схемы. Хотя казалось бы "Чего нам стоит дом построить? Нарисуем, будем жить". К слову, не раз видел, как люди пишут код, не проработав как следует логику, будучи уверены, что когда код будет написан, то и логика сама сабой разрешится. Этакий самообман программиста. Уверяю вас, так не бывает. Компьютер поймет вас ровно на столько, на сколько вы с ним изъяснитесь.
Ну да ладно, сейчас мы не будем сильно углубляться в логические детали, а рассмотрим пару технических моментов, с которыми я столкнулся, и попробуем разобраться в чем же здесь основы программирования и как это нам может помочь в освоении этого самого программирования.
Как я и сказал, здесь мы будем пользоваться базовыми средствами, практически не прибегая к сторонним готовым средствам. Первая задача - получить список запущенных контейнеров в докер-проекте. Запуск такого проекта расписывался в этой статье. Чтобы вывести список запущенных служб, надо в терминале выполнить команду docker-compose ps
Вот результат выполнения:
Теперь я хочу выполнить это средствами node-js. Для этого можно воспользоваться командами типа exec или spawn. Я понимаю, что рассмотрение этих команд детально - совсем не материал для начального уровня, но все же упомянуть их я должен. И тут же оставлю ссылочку на понравившуюся мне статью про эти команды (правда, она на англ): https://www.freecodecamp.org/news/node-js-child-processes-everything-you-need-to-know-e69498fe970a/
Не вдаваясь в подробности, объясню сам принцип: мы ноде говорим "выполни команду docker-compose ps" и верни нам результат, дальше мы сами решим что с этим результатом делать.
И вот тут я упомяну очень важную книгу, которую настоятельно советую прочитать каждому, хотя бы в формате художественной литературы. То есть даже если вы не будете по ней выполнять практические задания, все же прочитать ее будет очень полезно, чтобы иметь более четкое представление о том, что же такое программирование, как работают программы, какие существуют базовые типы данных и т.п.
Скачать книгу можно на официальном сайте автора: http://www.stolyarov.info/books/programming_intro/vol1
Ссылка на книгу (ее можно не заметить там): http://www.stolyarov.info/books/pdf/progintro_vol1.pdf
За себя скажу так: я ее прочитал относительно недавно (весной этого года), и хотя на тот момент уже имел довольно большой практический опыт в программировании, все же узнал для себя много нового и интересного. Особенно полезным было написание и выполнение программ в терминале. И хотя вам может показаться, что лично вам это скорее всего не понадобится (особенно если вы прокачиваетесь во фронт, типа там терминал совсем не нужен), на самом деле многие вещи очень взаимосвязаны. Во фронте вы так же будете работать со строками, числами, массивами, объектами и т.п. И если вы будете лучше понимать как это работает в принципе, то и во фронте вам будет сильно проще и понятней. А если вы потом захотите расшириться до fullstack, то тем более все это вам понадобится.
И вот в решении этой конкретной задачи с выполнением команды средствами ноды и обработкой результата мне как раз и помогли сильно знания, полученные благодаря этой книге. Где именно пригодились, я укажу в процессе.
Итак, вызывать извне команды мы будем посредством GraphQL-API. Для этого я написал небольшой резолвер и дал соответствующий метод. Вот результат:
Обратите сразу внимание на разницу того, в каком виде результат выводится в панели самого GraphQL Playground и в dev-tools. Особенно результат отличается во всплывашке (это я навел мышку в dev-tools на строку результата). Согласитесь, разница есть. Во всплывашке это больше похоже на то, что мы видели в самом терминале (то есть отформатировано, не имеет спецсимволов \n (которые есть суть знака переноса строки) и т.п.). То есть здесь мы сразу наблюдаем разницу между исходными данными и форматированными. Здесь надо понимать, что исходные данные - это просто одна строка. А форматированные - это обработанные программой для более понятного представления для простых пользователей. Это и есть суть UI, то есть пользовательские интерфейсы (User Interface). Но мы же программисты, там надо обработать эти данные и выполнять свою логику дальше. ОК, что мы с этим можем сделать? Как я и говорил, здесь есть спецсимволы \n, которые есть суть "перенос строки". То есть мы можем взять эту единую строку и разбить ее на массив отдельных строк, используя в качестве разделителя как раз этот спецсимвол \n.
Вот наша исходная строка:
Здесь я ее присвоил переменной str в консоли браузере. Чтобы было понятней, вот скрин:
Здесь мы опять не видим спецсимволы \n, так как консолька их скрывает, но надо помнить, что они там есть.
Есть и другие спецсимволы, такие как \t (символ табуляции), \r (перенос каретки. Работает практически так же как и \n, но все же есть отличия).
Так вот, сейчас я эту строку разобью на отдельные строки по знаку переноса. Использую для этого нативный метод split(). На выходе получу вот такой массив строк:
Здесь мы видим, что на выходе получился массив с 7 элементами, каждый из которых есть строка (напоминаю, что в массивах индексы элементов начинаются с 0. Элементов 7, но индексы их: 0, 1, 2, 3, 4, 5, 6). Но последняя строка пуская. Удалим ее, используя нативный метод filter(). В него я передаю в качестве аргумента функцию, которая будет применяться к каждому элементу этого массива для определения, нужен нам этот элемент или нет. Здесь функция простая: возвращает этот самый текущий элемент. Но он будет преобразован в истинность. Так как пустая строка при образовании к логическому дает ложь, то такой элемент будет пропущен. то есть "" == false, или Boolean("") === false
Как видите, здесь уже на выходе 6 элементов. Обратите внимание на синтаксис. Здесь нет присвоения результатов переменным. Здесь есть исходная строка, на ней вызывается метод .split("\n") и следом за ним вызывается метод .filter(n => n). Здесь надо понимать, что в результате выполнения каждого метода последовательно, следующий метод применяется к результату предыдущего метода. У нас была строка. К ней мы применили метод .split("\n"), в результате выполнения которого был получен массив, содержащий N строк. И вот к этому массиву (который никуда не был пока присвоен) мы применили метод .filter(n => n), в результате выполнения которого мы получили новый массив, содержащий строки, удовлетворяющие условию n => n. И далее по такой логике мы можем применять и другие методы, пока не получим конечный результат (что мы и будем делать далее).
Теперь у нас 6 строк в массиве. Но как мы видим, у нас первые две строки - это заголовки и разделитель. То есть нам они не нужны и их надо выкинуть из массива. Для этого используем метод .splice(2, 4). Здесь мы в текущем массиве взяли 4 элемента, начиная со второго и получили новый массив с этими элементами.
К слову, автор упомянутой книги, а именно Андрей Викторович Столяров, за такой стиль программирования меня бы наверняка сильно наругал. Говорю это точно, так как был у него на индивидуальном занятии и оно закончилось преждевременно :) Андрей Викторович такое называет "Сишность головного мозга" (по наименованию языка программирования C (Си))и совсем это не поощряет. Есть у него даже книга Оформление программного кода. Методическое пособие, в которой он дает рекомендации как правильней оформлять свой код, чтобы он был более понятный другим программистам (Ведь вполне вероятно ваш код будет читать кто-то другой). И вот этот самый код по идее должен быть написан как-то так:
И на выходе получить переменную array_services, содержащую наш отфильтрованный массив.
Но все это заставляет придумывать на лету множество именнованных переменных, что само по себе дополнительная задача. Плюс к этому еще и объем кода увеличивается. А чем больше кода, тем больше кода, тем хуже практически всегда (и воспринимать на самом деле больше приходится). Поэтому я сам больше тяготею к более компактному написанию. Так что извините. Но если где что не ясно, спрашивайте, я буду разъяснять.
Такое довольно объемное лирическое отступление получилось, но надеюсь, внес хоть немного ясности.
Возвращаясь к нашей задаче, напоминаю, что мы получили на выходе массив строк, каждая из которых содержит информацию об отдельном докер-контейнере. Но нам еще надо бы из каждой такой строки получить более подробную информацию. И вот здесь возникает сложность, потому что хотя мы и можем каждую такую строку разбить на отдельные части, но они не стандартизированы по количеству элементов. К примеру вот две строки:
docker-nextjs_mail_1 MailHog Up 0.0.0.0:1025->1025/tcp,:::1025->1025/tcp, 0.0.0.0:8025->8025/tcp,:::8025->8025/tcp
docker-nextjs_mysql_1 docker-entrypoint.sh mysqld Up 0.0.0.0:3306->3306/tcp,:::3306->3306/tcp, 33060/tcp
Как мы видим, между названиями служб и статусов (Up) у нас строки так же могут содержать пробелы. То есть мы заранее не можем знать сколько будет пробелов в команде и соответственно не можем просто так вычленить содержимое. Так что это практически тупик. На самом деле можно было бы еще поиграться и что-нибудь придумать (те же регулярные выражения заюзать). Можно было бы и вовсе заюзать команду docker-compose ps --services, которая возвращает именно список служб. Но в таком случае у нас кроме названия служб больше ничего и не было бы (а нужна более детальная информация). Соответственно, я начал смотреть какие более подходящие решения возможны. И тут наткнулся в документации, что разработчики докера теперь предлагают юзать не отдельную программу docker-compose (как мы это делали раньше и которую мы вызывали в наших примерах), а новый плагин для самого докера, который к программе docker добавляет команду (или подпрограмму) compose.
Устанавливается плагин так:
И вот в этой новой плагин-программе появляется новый параметр --format. И теперь вместо docker-compose ps мы можем выполнить docker compose ps --format json, чтобы получить результат в виде JSON-строки.
Обратите внимание, что это две совершенно разные команды, хоть и сильно похожие. В первом случае docker-compose ps - это программа docker-compose у которой мы вызываем команду ps, а во втором случае мы вызываем программу docker, у нее подпрограмму compose, а у той команду ps с флагом --format json
Даже вызов стправки на этих командах дает чуть разный результат.
И вот теперь мы на выходе имеем уже не просто строку, а JSON-строку.
Как мы видим, в результате парсинга мы получаем уже нормальные объекты с кучей структурированной полезной информации.
А можно на стороне GraphQL-сервера указать этому полю тип JSON и сразу в ответе получать структурированные данные.
Вот так, по сути, работая с довольно базовыми на самом деле вещами, можно получить интересный результат. И даже если вы планируете работать только с React (или Vue, или еще с чем-то, не важно), вы все равно будете работать с массивами, строками, преобразовывать их и т.п. Да, на многое будут готовые компоненты, но если вдруг чего-то не найдется и придется писать самому, то будет вам большим плюсом писать свой собственный код.
P.S. Если кто захочет поиграться с этой наработкой, я вылил ее сюда: https://github.com/Fi1osof/low-code
Актуальный коммит для этой статьи: https://github.com/Fi1osof/low-code/tree/ee56e5bdb60ef0baca50cdfc5e0cd878f9709051
Только имейте ввиду, что для безопасности я сразу прописал для этой команды правило доступа isSudo, так что для ее выполнения надо быть авторизованным пользователем с флагом sudo, и это не системный пользователь, а именно в рамках текущего JS-проекта. То есть надо иметь запущенный MySQL-сервер и связь с ним. Все это описано в статье Запуск тестового проекта локально. Токен авторизованного пользователя надо указать в заголовке Authorization
Николай, привет! Спасибо!
Дима, у тебя там все ОК. А ошибки в IDE в таких случаях - это не редкость. Дело в том, что когда ты внес изменения в призма-схему и перегенерил все, изменилось много файлов и типов. Тайпскрипт не держит все в голове и в такой момент вполне возникают ситуации, когда в кеше тайпскрипта не актуальные типы (или он еще не увидел новые типы). В таких случаях помогает рестарт тайпскрипт-сервера. В главном меню vscode выбери View -> Command Palette, а там найди TypeScript: Restart TS Server. Кликай его.
И немного жди. Занимает некоторое время. Внизу левее будет виден статус, что идет Initializing JS/TS. Как исчезнет, так ОК.
Еще важный момент: vscode бывает не ту версию тайпскрипта загружает. Из-за этого разное поведение при проверке типов (бывает в IDE совсем не то, что в консоли выполняется). Что надо сделать?
1. Проверить какая версия TS установлена в зависимостях.
yarn why typescript
Как видим, сейчас стоит 4.4.3
Смотрим в трее справа используемую текущую версию (Важно: должен быть открыт JS или TS файл для редактирования, чтобы IDE понимала, что сейчас нужен TS, иначе этой информации в трее просто не будет). Видим, что в IDE сейчас тоже используется 4.4.3. ОК, кликаем в 4.4.3 и выбираем в выпавшем меню Select typescript version.
И вот здесь как раз и кроется Дьявол: видно, что сейчас используется версия тайпскрипта, установленная в саму IDE. У меня все свежее установлено, поэтому версии и совпадают. Но часто бывает, что есть разница в версиях и бывает очень серьезная такая разница. В результате консольные команды дают одно, а в IDE мы видим другое. Надежней выбирать именно Use Workspace Version, то есть будет браться из node_modules текущего проекта.
Николай, добавил тип и запрос Like для поста: все работает, но здесь: https://github.com/linklib/gribok-prisma/blob/4ba6667eec2883af7536ba12d166c48c786bfabb/server/nexus/types/Post/index.ts#L26
IDE подсвечивает ошибку на ...like... (Property 'like' does not exist on type 'PrismaClient<PrismaClientOptions, never, RejectOnNotFound | RejectPerOperation | undefined>'.ts(2339) )
ни yarn types, ни yarn types:server ничего не находят.
Я закинул коммит --no-verify на всякий случай) Не посмотришь, что это за ошибка может быть?
Спасибо! Изучаю
Судя по всему ты просто опять забыл запустить yarn dev (о чем я уже не раз напоминал). То есть в целом у тебя там было ОК, просто надо было, чтобы нексус перегенерировал методы и типы (он это делает автоматически при изменении файлов, но надо, чтобы был запущен проект). Ну и конечно надо было удалить грибы из файла постов, чтобы у тебя дублей классов не было. Отправил ПР: https://github.com/linklib/gribok-prisma/pull/1
Ну и еще момент, который, как я и предполагал, ты не победишь, хотя следовало бы. Вот ты в грибах импортируешь класс поста. Обрати внимание, что у меня такого нигде нет. В том же типе User из текущего проекта много связей на другие типы, но там нет импортов этих типов. А все почему? Потому что в "type": у меня прописываются не константы-объекты, а строчные имена этих типов. Пересмотри еще раз видео, там тоже это все показывается/рассказывается. Обращай внмиание на типы переменных (видь разницу между объектами и простыми строками).
Всем привет!
Сегодня хочу написать немного филосовскую заметку. Просто случай занятный получился...
Буквально пару дней назад одному из своих учеников я написал следующее:
`
Стадия 1. Научиться делать так, как сказано.
Стадия 2. Понять почему это работает и почему это правильно
Стадия 3. Научиться это менять и делать по-другому.
Если ты не готов в Стадию 1, то ничего не получится. Я устану отвечать на разрозненные вопросы, которые никак не укладываются в общую канву.
`
Причиной написания послужило своеволие ученика, в чем я увидел лишнюю трату времени впустую, вместо того, чтобы потратить время более эффективно. Собственно, я на лету и обозначил три стадии, которые необходимо пройти. При чем если ученик не может придерживаться первой стадии, то я просто не смогу учить такого ученика (ученику придется самостоятельно продолжить обучение, или найти другого учителя, или вовсе забросить это дело).
А сегодня я случайно узнаю, что оказывается, есть такая японская концепция - СюХаРи. И что ее много где применяют, в том числе в обучении. Вот не плохая на мой взгляд заметка на этот счет: https://dev.by/news/syu-ha-ri-tri-stadii-obucheniya-i-nemnogo-nitsshe
И вот чем больше я почитал про это, тем больше убеждаюсь, что параллелий с моим видением на процесс обучения очень много. И именно поэтому я и вводил механизм менторства (потому что данный подход не подразумевает самостоятельного изучения с самого начала, должен быть учитель), вводил раздел уроков (чтобы были простейшие задачки потренироваться), вводил и стратегии развития (чтобы было понятно к чему стремиться и что изучать).
Конечно, каждый может самостоятельно учиться. Но поверьте, это будет сильно дольше. Возможно, при самостоятельном обучении вы в итоге будете знать материал сильно лучше, но все же скорее всего и времени потратите сильно больше. Я ставлю целью быстрейшее обучение необходимому, с чем можно было бы хотя бы на позицию джуна рассчитывать. А вот после того, как вы освоите необходимый минимум и время уже начнет работать на вас (зарплата капает), вот тогда уже можно начинать и вширь смотреть, больше самостоятельности проявлять.