Видео-вариант:
Когда-то давно, в 2015 году, я опубликовал на Хабре статью про Java программу, если вкратце "Как я на коленке сделал свое 1С:Предприятие, с блек-джеком и шлюхами".
Вот на что это было похоже (каскад форм создается автоматически по классам Entities с помощью reflection):

Но это была, во-первых, двухзвенка, а во-вторых, потребовала от меня столько лапшекода, что после кодирования я надолго погрузился в депрессию. И до сих пор считаю, что при прототипировании, ручное вколачивание Entities, еще и в параллель со скриптами liquibase - дурацкая работа, в то время как "тупые 1Сники" (по мнению Настоящих Программистов), редактируют структуру БД вообще не лазя в код и не напрягаясь. Теперь же я готов представить вам вариант производственного процесса, который не отпугнет перебежчика с 1С на Java.
Ключевое преимущество у 1С:Предприятия - заучив десяток хоткеев ты можешь налабать учетную систему, вообще не прикасаясь к мышке, просто добавляя элементы в дерево объектов конфигуратора.
С вашего позволения, процитирую свою же статью:
"Пров��дя тренинги для РП, я ... с нуля делал простейший контур складского учета за 4 минуты 35 секунд ..., включая ввод в базу тестового примера из двух документов."
Даже сложнейшие 10+ уровневые SQL запросы в 1С создаются без кодирования, выбором списка полей в специальном мастере. Не говоря обо всем остальном. Поэтому тогда, в 2015, я был глубоко разочарован Enities классами, hibernate и прочими прелестями Java.
И вот, допустим, джуниор с 1Сным бэкграундом решил податься за длинным рублем в мир Java/Scala (псс, если что, у нас в банке имеется пара вакансий, пишите в личку).
Но его мозг, избалованный 1Сом, сопротивляется - еще вчера ему не нужно было делать кучу дурацкой работы по набиванию Pojo/Hibernate классов на клаве, и сколько такого волка не корми высоким окладом, он все равно рано или поздно постарается слинять в лес из этого царства BDSM.
Я попытаюсь доказать, что и в мире Java/Scala есть инструменты, которые позволяют создавать полноценные трехзвенные (Bloody Enterprise) приложения, прикладывая минимум усилий, и используя максимум шикарных помощников.
Для начала, запрототипируем БД. Для этого можно использовать практически любую СУБД и любой редактор. В моем случае это PostgreSQL и DBeaver, но если лень устанавливать СУБД, рекомендую использовать H2 database. Создаем там базу, и таблицы по своей задаче. Я создал таблицу tasks для классического to-do list примера.

Кто же создаст для нас дурацкие java Pojo классы? Никто, но скаловские case классы сущностей создаст команда sbt slickCodeGenTask. Вот что она натворила (хо��я смотреть не обязательно, вряд ли она накосячит):
package com.todolist.shared // AUTO-GENERATED Slick data model /** Stand-alone Slick data model for immediate use */ object Tables extends { val profile = slick.jdbc.PostgresProfile } with Tables /** Slick data model trait for extension, choice of backend or usage in the cake pattern. (Make sure to initialize this late.) */ trait Tables { val profile: slick.jdbc.JdbcProfile import profile.api._ import slick.model.ForeignKeyAction // NOTE: GetResult mappers for plain SQL are only generated for tables where Slick knows how to map the types of all columns. import slick.jdbc.{GetResult => GR} /** DDL for all tables. Call .create to execute. */ lazy val schema: profile.SchemaDescription = Tasks.schema @deprecated("Use .schema instead of .ddl", "3.0") def ddl = schema /** Entity class storing rows of table Tasks * @param id Database column id SqlType(serial), AutoInc, PrimaryKey * @param text Database column text SqlType(varchar) * @param done Database column done SqlType(bool) */ case class TasksRow(id: Int, text: String, done: Boolean) /** GetResult implicit for fetching TasksRow objects using plain SQL queries */ implicit def GetResultTasksRow(implicit e0: GR[Int], e1: GR[String], e2: GR[Boolean]): GR[TasksRow] = GR{ prs => import prs._ TasksRow.tupled((<<[Int], <<[String], <<[Boolean])) } /** Table description of table tasks. Objects of this class serve as prototypes for rows in queries. */ class Tasks(_tableTag: Tag) extends profile.api.Table[TasksRow](_tableTag, "tasks") { def * = (id, text, done) <> (TasksRow.tupled, TasksRow.unapply) /** Maps whole row to an option. Useful for outer joins. */ def ? = ((Rep.Some(id), Rep.Some(text), Rep.Some(done))).shaped.<>({r=>import r._; _1.map(_=> TasksRow.tupled((_1.get, _2.get, _3.get)))}, (_:Any) => throw new Exception("Inserting into ? projection not supported.")) /** Database column id SqlType(serial), AutoInc, PrimaryKey */ val id: Rep[Int] = column[Int]("id", O.AutoInc, O.PrimaryKey) /** Database column text SqlType(varchar) */ val text: Rep[String] = column[String]("text") /** Database column done SqlType(bool) */ val done: Rep[Boolean] = column[Boolean]("done") } /** Collection-like TableQuery object for table Tasks */ lazy val Tasks = new TableQuery(tag => new Tasks(tag)) }
После того, как IDEA синхронизирует ваш проект, все сущности, которые подтянулись из SQL схемы, будут подсвечиваться и подсказываться по Ctrl + Space, в т.ч. в любом лямбда-выражении и for comprehension:

Что же в проекте предстоит закодировать нам лично? Всего 2 файла, сервера и клиента. Делать это мы будем, используя фреймворк Akka и концепцию акторов. Очень удобно, что акторы (клиент и сервер) могут относится к разным актор-системам и запускаться в разных концах интернета, обмениваясь сообщениями посредством TCP, UDP (могут и по HTTP/HTTPS, но об этом в другой раз).
Итак, код сервера:
package com.todolist import akka.actor.{Actor, ActorRef, ActorSystem, Props} import com.typesafe.config.ConfigFactory import java.util.concurrent.Executors import scala.concurrent.ExecutionContext import scala.util.Success object Server extends App{ val config =ConfigFactory.load("application") val actorSystem = ActorSystem("serversystem", config) val server: ActorRef = actorSystem.actorOf(Props[ToDoListServer],"todolistserver") } class ToDoListServer extends Actor{ import slick.driver.PostgresDriver.api._ implicit val db = Database.forConfig("my.myDb",Server.config) implicit val ec = ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(10)) override def receive: Receive = { case x:String if (x.trim.isEmpty) => case x:String => val snd = sender() val arr = x.split(' ') arr(0) match { case "list" => db.run(com.todolist.shared.Tables.Tasks.filter(_.done === false).sortBy(_.id).result) .onComplete{ e => e match { case Success(e) => val res = e.map(e=>s"${e.id}. ${e.text}").mkString("\n") println(res) snd ! res }} case "add" => db.run(com.todolist.shared.Tables.Tasks += com.todolist.shared.Tables.TasksRow(text = arr.drop(1).mkString(" "),done = false,id=0)) case "done" => val arr1 = arr(1).toInt db.run((for { c <- com.todolist.shared.Tables.Tasks if c.id === arr1 } yield c.done).update(true)) case _ => sender() ! "Unknown command" } } }
Как видите, никакого plain SQL - и одновременно, нет необходимости руками мапить структуру БД на case классы Entities.
Код клиента (вообще-то, я изначально скопировал его с какого-то примера из интернета, но так как ввод с клавы внутри актора глючил, от примера по сути ничего не осталось):
package com.todolist import akka.actor.{Actor, ActorRef, ActorSystem, Props} object Client { case class Connect(server: ActorRef) case class Process(cmd: String) } class Client extends Actor { override def receive: Actor.Receive = { case Client.Connect(server) => context become process(server) } def process(server: ActorRef): Receive = { case Client.Process(cmd) => server ! cmd case x:String => println(s"$x") case _ => } } object ClientApp extends App { import com.typesafe.config.ConfigFactory import scala.concurrent.duration._ val actorSystem = ActorSystem("client-system", ConfigFactory.load("client")) val client: ActorRef = actorSystem.actorOf(Props[Client]) val serverPath = "akka://serversystem@127.0.0.1:2552/user/todolistserver" val serverSelection = actorSystem.actorSelection(serverPath) serverSelection.resolveOne(FiniteDuration(10, SECONDS)).foreach { (server: ActorRef) => println(s"Connected to $server") client ! Client.Connect(server) while (true){ println("Enter command:") scala.io.StdIn.readLine() match { case cmd => client ! Client.Process(cmd) } } } (actorSystem.dispatcher) }
Актор-системы сервера и клиента, если они расположены на одном хосте, не могут висеть не одном и том же порту, поэтому, должны быть сконфигурированы разными файлами application.conf, client.conf. Кроме этого, для sbt придется вырубить jmx, там тоже происходит конфликт портов (либо, используйте разные конфиги и для sbt клиента и сервера).
Для запуска, в одном терминале выполняем:
sbt "runMain com.todolist.Server"
В другом:
sbt "runMain com.todolist.ClientApp"
Проверка:
Enter command: list
5. task2
7. task3
9. task5
Enter command: add task6
Enter command: list
5. task2
7. task3
9. task5
10. task6
Enter command: done 9
Enter command: list
5. task2
7. task3
10. task6
В данном, не побоюсь этого слова, трехзвенном приложении, я лично закодил не более 80 строк; при этом, после создания схемы БД и автоматическом импорте ее в приложение, IDEA подсказывала мне, какие поля имеются у сущностей, и не налепил ли я ошибок. В принципе, это то, что делает конструктор запросов в 1С:Предприятии, поскольку ни строчки plain SQL я не написал. Но, конструктор запросов в 1С может делать только select`ы, все остальное предстоит делать самостоятельно - а тут, как видите, и Insert, и Update.
А вообще-то, в таком же стиле, на Slick вы можете закодить мега запросы для Spark SQL, которые распараллелятся и выполнятся на hadoop кластере из 1000 серверов, а потом обработать это все каким-нибудь XGBoost или Tensorflow. В общем, все только начинается...
