Видео-вариант:

Когда-то давно, в 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. В общем, все только начинается...

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Что бы вы выбрали, если завтра сдавать проект учетной системы, а вы даже не приступали?
39.13%Java + Spring36
9.78%Scala + Akka9
22.83%1C: Предприятие 821
3.26%SAP3
6.52%PHP + WordPress6
18.48%Этот вызов мне не по зубам17
Проголосовали 92 пользователя. Воздержались 24 пользователя.