рдмреНрдпрд╛рдЪ рдХреНрд╡реЗрд░реА рдкреНрд░рд╢реЛрдзрдирдХрд╛ рд╕рдорд╕реНрдпрд╛рд╣рд░реВ рд░ рддрд┐рдирдХрд╛ рд╕рдорд╛рдзрд╛рдирд╣рд░реВ (рднрд╛рдЧ рез)

рдмреНрдпрд╛рдЪ рдХреНрд╡реЗрд░реА рдкреНрд░рд╢реЛрдзрдирдХрд╛ рд╕рдорд╕реНрдпрд╛рд╣рд░реВ рд░ рддрд┐рдирдХрд╛ рд╕рдорд╛рдзрд╛рдирд╣рд░реВ (рднрд╛рдЧ рез)рд▓рдЧрднрдЧ рд╕рдмреИ рдЖрдзреБрдирд┐рдХ рд╕рдлреНрдЯрд╡реЗрдпрд░ рдЙрддреНрдкрд╛рджрдирд╣рд░реВрдорд╛ рдзреЗрд░реИ рд╕реЗрд╡рд╛рд╣рд░реВ рд╣реБрдиреНрдЫрдиреНред рдЕрдХреНрд╕рд░, рдЗрдиреНрдЯрд░рд╕рд░реНрднрд┐рд╕ рдЪреНрдпрд╛рдирд▓рд╣рд░реВрдХреЛ рд▓рд╛рдореЛ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рд╕рдордп рдкреНрд░рджрд░реНрд╢рди рд╕рдорд╕реНрдпрд╛рд╣рд░реВрдХреЛ рд╕реНрд░реЛрдд рдмрдиреНрдЫред рдпрд╕ рдкреНрд░рдХрд╛рд░рдХреЛ рд╕рдорд╕реНрдпрд╛рдХреЛ рдорд╛рдирдХ рд╕рдорд╛рдзрд╛рди рднрдиреЗрдХреЛ рдзреЗрд░реИ рдЕрдиреНрддрд░рд╕реЗрд╡рд╛ рдЕрдиреБрд░реЛрдзрд╣рд░реВрд▓рд╛рдИ рдПрдЙрдЯрд╛ рдкреНрдпрд╛рдХреЗрдЬрдорд╛ рдкреНрдпрд╛рдХ рдЧрд░реНрдиреБ рд╣реЛ, рдЬрд╕рд▓рд╛рдИ рдмреНрдпрд╛рдЪрд┐рдЩ рднрдирд┐рдиреНрдЫред

рдпрджрд┐ рддрдкрд╛рдЗрдБ рдмреНрдпрд╛рдЪ рдкреНрд░рд╢реЛрдзрди рдкреНрд░рдпреЛрдЧ рдЧрд░реНрдиреБрд╣реБрдиреНрдЫ рднрдиреЗ, рддрдкрд╛рдЗрдБ рдкреНрд░рджрд░реНрд╢рди рд╡рд╛ рдХреЛрдб рд╕реНрдкрд╖реНрдЯрддрд╛рдХреЛ рд╕рд░реНрддрдорд╛ рдирддрд┐рдЬрд╛рд╣рд░реВрд╕рдБрдЧ рдЦреБрд╕реА рдирд╣реБрди рд╕рдХреНрдиреБрд╣реБрдиреНрдЫред рдпреЛ рд╡рд┐рдзрд┐ рдХрд▓рд░рдорд╛ рддрдкрд╛рдИрд▓реЗ рд╕реЛрдЪреЗ рдЬрд╕реНрддреЛ рд╕рдЬрд┐рд▓реЛ рдЫреИрдиред рд╡рд┐рднрд┐рдиреНрди рдЙрджреНрджреЗрд╢реНрдпрдХрд╛ рд▓рд╛рдЧрд┐ рд░ рд╡рд┐рднрд┐рдиреНрди рдкрд░рд┐рд╕реНрдерд┐рддрд┐рд╣рд░реВрдорд╛, рд╕рдорд╛рдзрд╛рдирд╣рд░реВ рдзреЗрд░реИ рднрд┐рдиреНрди рд╣реБрди рд╕рдХреНрдЫрдиреНред рд╡рд┐рд╢рд┐рд╖реНрдЯ рдЙрджрд╛рд╣рд░рдгрд╣рд░реВ рдкреНрд░рдпреЛрдЧ рдЧрд░реЗрд░, рдо рдзреЗрд░реИ рджреГрд╖реНрдЯрд┐рдХреЛрдгрд╣рд░реВрдХреЛ рдлрд╛рдЗрджрд╛ рд░ рд╡рд┐рдкрдХреНрд╖ рджреЗрдЦрд╛рдЙрдиреЗрдЫреБред

рдкреНрд░рджрд░реНрд╢рди рдкрд░рд┐рдпреЛрдЬрдирд╛

рд╕реНрдкрд╖реНрдЯрддрд╛рдХреЛ рд▓рд╛рдЧрд┐, рдо рд╣рд╛рд▓ рдХрд╛рдо рдЧрд░рд┐рд░рд╣реЗрдХреЛ рдЕрдиреБрдкреНрд░рдпреЛрдЧрдорд╛ рд╕реЗрд╡рд╛рд╣рд░реВ рдордзреНрдпреЗ рдПрдЙрдЯрд╛рдХреЛ рдЙрджрд╛рд╣рд░рдг рд╣реЗрд░реМрдВред

рдЙрджрд╛рд╣рд░рдгрдХрд╛ рд▓рд╛рдЧрд┐ рдкреНрд▓реЗрдЯрдлрд░реНрдо рдЪрдпрдирдХреЛ рд╡реНрдпрд╛рдЦреНрдпрд╛рдЦрд░рд╛рдм рдкреНрд░рджрд░реНрд╢рдирдХреЛ рд╕рдорд╕реНрдпрд╛ рдПрдХрджрдо рд╕рд╛рдорд╛рдиреНрдп рдЫ рд░ рдХреБрдиреИ рд╡рд┐рд╢реЗрд╖ рднрд╛рд╖рд╛ рд╡рд╛ рдкреНрд▓реЗрдЯрдлрд░реНрдорд╣рд░реВрд▓рд╛рдИ рдЕрд╕рд░ рдЧрд░реНрджреИрдиред рдпрд╕ рд▓реЗрдЦрд▓реЗ рд╕рдорд╕реНрдпрд╛ рд░ рд╕рдорд╛рдзрд╛рдирд╣рд░реВ рдкреНрд░рджрд░реНрд╢рди рдЧрд░реНрди рд╡рд╕рдиреНрдд + рдХреЛрдЯрд▓рд┐рди рдХреЛрдб рдЙрджрд╛рд╣рд░рдгрд╣рд░реВ рдкреНрд░рдпреЛрдЧ рдЧрд░реНрдиреЗрдЫред Kotlin рдЬрд╛рднрд╛ рд░ C# рд╡рд┐рдХрд╛рд╕рдХрд░реНрддрд╛рд╣рд░реВрдХреЛ рд▓рд╛рдЧрд┐ рд╕рдорд╛рди рд░реВрдкрдорд╛ рдмреБрдЭреНрди рдпреЛрдЧреНрдп (рд╡рд╛ рдмреБрдЭреНрди рдирд╕рдХрд┐рдиреЗ) рдЫ, рдердк рд░реВрдкрдорд╛, рдХреЛрдб рдЬрд╛рднрд╛ рднрдиреНрджрд╛ рдмрдвреА рдХрдореНрдкреНрдпрд╛рдХреНрдЯ рд░ рдмреБрдЭреНрди рдпреЛрдЧреНрдп рдЫред рд╢реБрджреНрдз рдЬрд╛рднрд╛ рд╡рд┐рдХрд╛рд╕рдХрд░реНрддрд╛рд╣рд░реВрдХрд╛ рд▓рд╛рдЧрд┐ рдмреБрдЭреНрди рд╕рдЬрд┐рд▓реЛ рдмрдирд╛рдЙрдирдХреЛ рд▓рд╛рдЧрд┐, рдо рдХреЛрдЯрд▓рд┐рдирдХреЛ рдХрд╛рд▓реЛ рдЬрд╛рджреВрд▓рд╛рдИ рдмреЗрд╡рд╛рд╕реНрддрд╛ рдЧрд░реНрдиреЗрдЫреБ рд░ рдХреЗрд╡рд▓ рд╕реЗрддреЛ рдЬрд╛рджреВ (рд▓реЛрдореНрдмреЛрдХрдХреЛ рднрд╛рд╡рдирд╛рдорд╛) рдкреНрд░рдпреЛрдЧ рдЧрд░реНрдиреЗрдЫреБред рддреНрдпрд╣рд╛рдБ рдХреЗрд╣рд┐ рд╡рд┐рд╕реНрддрд╛рд░ рд╡рд┐рдзрд┐рд╣рд░реВ рд╣реБрдиреЗрдЫрдиреН, рддрд░ рддрд┐рдиреАрд╣рд░реВ рд╡рд╛рд╕реНрддрд╡рдорд╛ рд╕рдмреИ рдЬрд╛рднрд╛ рдкреНрд░реЛрдЧреНрд░рд╛рдорд░рд╣рд░реВрд▓рд╛рдИ рд╕реНрдерд┐рд░ рд╡рд┐рдзрд┐рд╣рд░реВрдХреЛ рд░реВрдкрдорд╛ рдкрд░рд┐рдЪрд┐рдд рдЫрдиреН, рддреНрдпрд╕реИрд▓реЗ рдпреЛ рд╕рд╛рдиреЛ рдЪрд┐рдиреА рд╣реБрдиреЗрдЫ рдЬрд╕рд▓реЗ рдбрд┐рд╢рдХреЛ рд╕реНрд╡рд╛рдж рдмрд┐рдЧрд╛рд░реНрджреИрдиред
рддреНрдпрд╣рд╛рдБ рдХрд╛рдЧрдЬрд╛рдд рдЕрдиреБрдореЛрджрди рд╕реЗрд╡рд╛ рдЫред рдХрд╕реИрд▓реЗ рдХрд╛рдЧрдЬрд╛рдд рд╕рд┐рд░реНрдЬрдирд╛ рдЧрд░реНрдЫ рд░ рдпрд╕рд▓рд╛рдИ рдЫрд▓рдлрд▓рдХреЛ рд▓рд╛рдЧрд┐ рдкреЗрд╢ рдЧрд░реНрджрдЫ, рдЬрд╕рдорд╛ рд╕рдореНрдкрд╛рджрдирд╣рд░реВ рдЧрд░рд┐рдиреНрдЫрдиреН, рд░ рдЕрдиреНрддрддрдГ рдХрд╛рдЧрдЬрд╛рддрдорд╛ рд╕рд╣рдорддрд┐ рд╣реБрдиреНрдЫред рд╕реНрд╡реАрдХреГрддрд┐ рд╕реЗрд╡рд╛ рдЖрдлреИрдВрд▓реЗ рдХрд╛рдЧрдЬрд╛рддрд╣рд░реВрдХреЛ рдмрд╛рд░реЗрдорд╛ рдХреЗрд╣рд┐ рдерд╛рд╣рд╛ рдЫреИрди: рдпреЛ рдХреЗрд╡рд▓ рд╕рд╛рдирд╛ рдЕрддрд┐рд░рд┐рдХреНрдд рдкреНрд░рдХрд╛рд░реНрдпрд╣рд░реВрдХреЛ рд╕рд╛рде рдЕрдиреБрдореЛрджрдирдХрд░реНрддрд╛рд╣рд░реВрдХреЛ рдЪреНрдпрд╛рдЯ рд╣реЛ рдЬреБрди рд╣рд╛рдореА рдпрд╣рд╛рдБ рд╡рд┐рдЪрд╛рд░ рдЧрд░реНрджреИрдиреМрдВред

рддреНрдпрд╕реЛрднрдП, рддреНрдпрд╣рд╛рдБ рдкреНрд░рддреНрдпреЗрдХрдорд╛ рд╕рд╣рднрд╛рдЧреАрд╣рд░реВрдХреЛ рдкреВрд░реНрд╡рдирд┐рд░реНрдзрд╛рд░рд┐рдд рд╕реЗрдЯрдХреЛ рд╕рд╛рде рдЪреНрдпрд╛рдЯ рдХреЛрдард╛рд╣рд░реВ (рдХрд╛рдЧрдЬрд╛рддрд╣рд░реВрд╕рдБрдЧ рд╕рдореНрдмрдиреНрдзрд┐рдд) рдЫрдиреНред рдирд┐рдпрдорд┐рдд рдЪреНрдпрд╛рдЯрд╣рд░реВрдорд╛ рдЬрд╕реНрддреИ, рд╕рдиреНрджреЗрд╢рд╣рд░реВрдорд╛ рдкрд╛рда рд░ рдлрд╛рдЗрд▓рд╣рд░реВ рдЫрдиреН рд░ рдЬрд╡рд╛рдл рд╡рд╛ рдлрд░реНрд╡рд╛рд░реНрдб рдЧрд░реНрди рд╕рдХрд┐рдиреНрдЫ:

data class ChatMessage(
  // nullable ╤В╨░╨║ ╨║╨░╨║ ╨┐╨╛╤П╨▓╨╗╤П╨╡╤В╤Б╤П ╤В╨╛╨╗╤М╨║╨╛ ╨┐╨╛╤Б╨╗╨╡ persist
  val id: Long? = null,
  /** ╨б╤Б╤Л╨╗╨║╨░ ╨╜╨░ ╨░╨▓╤В╨╛╤А╨░ */
  val author: UserReference,
  /** ╨б╨╛╨╛╨▒╤Й╨╡╨╜╨╕╨╡ */
  val message: String,
  /** ╨б╤Б╤Л╨╗╨║╨╕ ╨╜╨░ ╨░╤В╤В╨░╤З╨╕ */
  // ╨╕╨╖-╨╖╨░ ╨╛╤Б╨╛╨▒╨╡╨╜╨╜╨╛╤Б╤В╨╡╨╣ ╤Б╨▓╤П╨╖╨║╨╕ JPA+╨б╨г╨С╨Ф ╨┐╤А╨╛╤Й╨╡ ╨┐╨╛╨┤╨┤╨╡╤А╨╢╨╕╨▓╨░╤В╤М ╨╕ null, ╨╕ ╨┐╤Г╤Б╤В╤Л╨╡ ╤Б╨┐╨╕╤Б╨║╨╕
  val files: List<FileReference>? = null,
  /** ╨Х╤Б╨╗╨╕ ╤П╨▓╨╗╤П╨╡╤В╤Б╤П ╨╛╤В╨▓╨╡╤В╨╛╨╝, ╤В╨╛ ╨╖╨┤╨╡╤Б╤М ╨▒╤Г╨┤╨╡╤В ╨╛╤А╨╕╨│╨╕╨╜╨░╨╗ */
  val replyTo: ChatMessage? = null,
  /** ╨Х╤Б╨╗╨╕ ╤П╨▓╨╗╤П╨╡╤В╤Б╤П ╨┐╨╡╤А╨╡╤Б╤Л╨╗╨║╨╛╨╣, ╤В╨╛ ╨╖╨┤╨╡╤Б╤М ╨▒╤Г╨┤╨╡╤В ╨╛╤А╨╕╨│╨╕╨╜╨░╨╗ */
  val forwardFrom: ChatMessage? = null
)

рдлрд╛рдЗрд▓ рд░ рдкреНрд░рдпреЛрдЧрдХрд░реНрддрд╛ рд▓рд┐рдЩреНрдХрд╣рд░реВ рдЕрдиреНрдп рдбреЛрдореЗрдирд╣рд░реВрдорд╛ рд▓рд┐рдЩреНрдХрд╣рд░реВ рд╣реБрдиреНред рдпрд╣рд╛рдБ рд╣рд╛рдореА рдпрд╕рд░реА рдмрд╛рдБрдЪрд┐рд░рд╣реЗрдХрд╛ рдЫреМрдВ:

typealias FileReference Long
typealias UserReference Long

рдкреНрд░рдпреЛрдЧрдХрд░реНрддрд╛ рдбреЗрдЯрд╛ Keycloak рдорд╛ рднрдгреНрдбрд╛рд░рдг рдЧрд░рд┐рдиреНрдЫ рд░ REST рдорд╛рд░реНрдлрдд рдкреБрдирдГрдкреНрд░рд╛рдкреНрдд рдЧрд░рд┐рдиреНрдЫред рдлрд╛рдЗрд▓рд╣рд░реВрдХреЛ рд▓рд╛рдЧрд┐ рдкрдирд┐ рдЙрд╕реНрддреИ рдЬрд╛рдиреНрдЫ: рдлрд╛рдЗрд▓рд╣рд░реВ рд░ рддрд┐рдиреАрд╣рд░реВрдХрд╛ рдмрд╛рд░реЗ рдореЗрдЯреЗрдирдлрд░реНрдореЗрд╕рдирд╣рд░реВ рдЫреБрдЯреНрдЯреИ рдлрд╛рдЗрд▓ рднрдгреНрдбрд╛рд░рдг рд╕реЗрд╡рд╛рдорд╛ рдмрд╕реНрдЫрдиреНред

рдпреА рд╕реЗрд╡рд╛рд╣рд░реВрдорд╛ рд╕рдмреИ рдХрд▓рд╣рд░реВ рдЫрдиреН рднрд╛рд░реА рдЕрдиреБрд░реЛрдзред рдпрд╕рдХреЛ рдорддрд▓рдм рдпреА рдЕрдиреБрд░реЛрдзрд╣рд░реВ рдвреБрд╡рд╛рдиреАрдХреЛ рдУрднрд░рд╣реЗрдб рддреЗрд╕реНрд░реЛ-рдкрдХреНрд╖ рд╕реЗрд╡рд╛рджреНрд╡рд╛рд░рд╛ рдкреНрд░рд╢реЛрдзрди рдЧрд░реНрди рд▓рд╛рдЧреНрдиреЗ рд╕рдордп рднрдиреНрджрд╛ рдзреЗрд░реИ рд╣реЛред рд╣рд╛рдореНрд░реЛ рдкрд░реАрдХреНрд╖рдг рдмреЗрдиреНрдЪрд╣рд░реВрдорд╛, рддреНрдпрд╕реНрддрд╛ рд╕реЗрд╡рд╛рд╣рд░реВрдХреЛ рд▓рд╛рдЧрд┐ рд╕рд╛рдорд╛рдиреНрдп рдХрд▓ рд╕рдордп 100 ms рд╣реЛ, рддреНрдпрд╕реИрд▓реЗ рд╣рд╛рдореА рднрд╡рд┐рд╖реНрдпрдорд╛ рдпреА рдирдореНрдмрд░рд╣рд░реВ рдкреНрд░рдпреЛрдЧ рдЧрд░реНрдиреЗрдЫреМрдВред

рд╣рд╛рдореАрд▓реЗ рд╕рдмреИ рдЖрд╡рд╢реНрдпрдХ рдЬрд╛рдирдХрд╛рд░реАрдХреЛ рд╕рд╛рде рдЕрдиреНрддрд┐рдо N рд╕рдиреНрджреЗрд╢рд╣рд░реВ рдкреНрд░рд╛рдкреНрдд рдЧрд░реНрди рдПрдХ рд╕рд╛рдзрд╛рд░рдг REST рдирд┐рдпрдиреНрддреНрд░рдХ рдмрдирд╛рдЙрди рдЖрд╡рд╢реНрдпрдХ рдЫред рддреНрдпреЛ рд╣реЛ, рд╣рд╛рдореА рд╡рд┐рд╢реНрд╡рд╛рд╕ рдЧрд░реНрдЫреМрдВ рдХрд┐ рдлреНрд░рдиреНрдЯрдПрдиреНрдбрдорд╛ рд╕рдиреНрджреЗрд╢ рдореЛрдбреЗрд▓ рд▓рдЧрднрдЧ рд╕рдорд╛рди рдЫ рд░ рд╕рдмреИ рдбреЗрдЯрд╛ рдкрдард╛рдЙрди рдЖрд╡рд╢реНрдпрдХ рдЫред рдлреНрд░рдиреНрдЯ-рдПрдиреНрдб рдореЛрдбреЗрд▓ рдмреАрдЪрдХреЛ рднрд┐рдиреНрдирддрд╛ рднрдиреЗрдХреЛ рдлрд╛рдЗрд▓ рд░ рдкреНрд░рдпреЛрдЧрдХрд░реНрддрд╛рд▓рд╛рдИ рд▓рд┐рдЩреНрдХрд╣рд░реВ рдмрдирд╛рдЙрдирдХреЛ рд▓рд╛рдЧрд┐ рдереЛрд░реИ рдбрд┐рдХреНрд░рд┐рдкреНрдЯреЗрдб рдлрд╛рд░рдордорд╛ рдкреНрд░рд╕реНрддреБрдд рдЧрд░реНрди рдЖрд╡рд╢реНрдпрдХ рдЫ:

/** ╨Т ╤В╨░╨║╨╛╨╝ ╨▓╨╕╨┤╨╡ ╨╛╤В╨┤╨░╤О╤В╤Б╤П ╤Б╤Б╤Л╨╗╨║╨╕ ╨╜╨░ ╤Б╤Г╤Й╨╜╨╛╤Б╤В╨╕ ╨┤╨╗╤П ╤Д╤А╨╛╨╜╤В╨░ */
data class ReferenceUI(
  /** ╨Ш╨┤╨╡╨╜╤В╨╕╤Д╨╕╨║╨░╤В╨╛╤А ╨┤╨╗╤П url */
  val ref: String,
  /** ╨Т╨╕╨┤╨╕╨╝╨╛╨╡ ╨┐╨╛╨╗╤М╨╖╨╛╨▓╨░╤В╨╡╨╗╤О ╨╜╨░╨╖╨▓╨░╨╜╨╕╨╡ ╤Б╤Б╤Л╨╗╨║╨╕ */
  val name: String
)
data class ChatMessageUI(
  val id: Long,
  /** ╨б╤Б╤Л╨╗╨║╨░ ╨╜╨░ ╨░╨▓╤В╨╛╤А╨░ */
  val author: ReferenceUI,
  /** ╨б╨╛╨╛╨▒╤Й╨╡╨╜╨╕╨╡ */
  val message: String,
  /** ╨б╤Б╤Л╨╗╨║╨╕ ╨╜╨░ ╨░╤В╤В╨░╤З╨╕ */
  val files: List<ReferenceUI>,
  /** ╨Х╤Б╨╗╨╕ ╤П╨▓╨╗╤П╤В╤Б╤П ╨╛╤В╨▓╨╡╤В╨╛╨╝, ╤В╨╛ ╨╖╨┤╨╡╤Б╤М ╨▒╤Г╨┤╨╡╤В ╨╛╤А╨╕╨│╨╕╨╜╨░╨╗ */
  val replyTo: ChatMessageUI? = null,
  /** ╨Х╤Б╨╗╨╕ ╤П╨▓╨╗╤П╤В╤Б╤П ╨┐╨╡╤А╨╡╤Б╤Л╨╗╨║╨╛╨╣, ╤В╨╛ ╨╖╨┤╨╡╤Б╤М ╨▒╤Г╨┤╨╡╤В ╨╛╤А╨╕╨│╨╕╨╜╨░╨╗ */
  val forwardFrom: ChatMessageUI? = null
)

рд╣рд╛рдореАрд▓реЗ рдирд┐рдореНрди рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдЧрд░реНрди рдЖрд╡рд╢реНрдпрдХ рдЫ:

interface ChatRestApi {
  fun getLast(nInt): List<ChatMessageUI>
}

UI рдкреЛрд╕реНрдЯрдлрд┐рдХреНрд╕ рднрдиреЗрдХреЛ рдлреНрд░рдиреНрдЯрдПрдиреНрдбрдХрд╛ рд▓рд╛рдЧрд┐ DTO рдореЛрдбреЗрд▓рд╣рд░реВ рд╣реЛ, рддреНрдпреЛ рд╣реЛ, рд╣рд╛рдореАрд▓реЗ REST рдорд╛рд░реНрдлрдд рдХреЗ рд╕реЗрд╡рд╛ рдЧрд░реНрдиреБрдкрд░реНрдЫред

рдпрд╣рд╛рдБ рдЕрдЪрдореНрдордХреЛ рдХреБрд░рд╛ рдХреЗ рд╣реБрди рд╕рдХреНрдЫ рдХрд┐ рд╣рд╛рдореАрд▓реЗ рдХреБрдиреИ рдЪреНрдпрд╛рдЯ ID рдкрд╛рд╕ рдЧрд░рд┐рд░рд╣реЗрдХрд╛ рдЫреИрдиреМрдВ рд░ ChatMessage/ChatMessageUI рдореЛрдбреЗрд▓рдорд╛ рдкрдирд┐ рдЫреИрдиред рдореИрд▓реЗ рдпреЛ рдЬрд╛рдирд╛рдЬрд╛рдиреА рдЧрд░реЗрдВ рддрд╛рдХрд┐ рдЙрджрд╛рд╣рд░рдгрд╣рд░реВрдХреЛ рдХреЛрдб рдЕрд╡реНрдпрд╡рд╕реНрдерд┐рдд рдирд╣реЛрд╕реН (рдЪреНрдпрд╛рдЯрд╣рд░реВ рдЕрд▓рдЧ рдЫрдиреН, рддреНрдпрд╕реИрд▓реЗ рд╣рд╛рдореА рдорд╛рдиреНрди рд╕рдХреНрдЫреМрдВ рдХрд┐ рд╣рд╛рдореАрд╕рдБрдЧ рдПрдЙрдЯрд╛ рдорд╛рддреНрд░ рдЫ)ред

рджрд╛рд░реНрд╢рдирд┐рдХ рд╡рд┐рд╖рдпрд╡рд╕реНрддреБрджреБрдмреИ ChatMessageUI рдХрдХреНрд╖рд╛ рд░ ChatRestApi.getLast рд╡рд┐рдзрд┐рд▓реЗ рд╕реВрдЪреА рдбреЗрдЯрд╛ рдкреНрд░рдХрд╛рд░ рдкреНрд░рдпреЛрдЧ рдЧрд░реНрджрдЫ рдЬрдм рд╡рд╛рд╕реНрддрд╡рдорд╛ рдпреЛ рдЕрд░реНрдбрд░ рдЧрд░рд┐рдПрдХреЛ рд╕реЗрдЯ рд╣реЛред рдпреЛ JDK рдорд╛ рдЦрд░рд╛рдм рдЫ, рддреНрдпрд╕реИрд▓реЗ рдЗрдиреНрдЯрд░рдлреЗрд╕ рд╕реНрддрд░рдорд╛ рддрддреНрд╡рд╣рд░реВрдХреЛ рдХреНрд░рдо рдШреЛрд╖рдгрд╛ рдЧрд░реНрди (рдердкреНрджрд╛ рд░ рд╣рдЯрд╛рдЙрдиреЗ рдХреНрд░рдордорд╛ рдЕрд░реНрдбрд░ рд╕реБрд░рдХреНрд╖рд┐рдд рдЧрд░реНрдиреЗ) рдХрд╛рдо рдЧрд░реНрджреИрдиред рддреНрдпрд╕рдХрд╛рд░рдг рдЕрд░реНрдбрд░ рдЧрд░рд┐рдПрдХреЛ рд╕реЗрдЯ рдЖрд╡рд╢реНрдпрдХ рднрдПрдХреЛ рдЕрд╡рд╕реНрдерд╛рдорд╛ рд╕реВрдЪреА рдкреНрд░рдпреЛрдЧ рдЧрд░реНрдиреЗ рд╕рд╛рдорд╛рдиреНрдп рдЕрднреНрдпрд╛рд╕ рднрдПрдХреЛ рдЫ (рддреНрдпрд╣рд╛рдБ рд▓рд┐рдЩреНрдХрдбрд╣реНрдпрд╛рд╕рд╕реЗрдЯ рдкрдирд┐ рдЫ, рддрд░ рдпреЛ рдЗрдиреНрдЯрд░рдлреЗрд╕ рд╣реЛрдЗрди)ред
рдорд╣рддреНрддреНрд╡рдкреВрд░реНрдг рд╕реАрдорд╛: рдЬрд╡рд╛рдл рд╡рд╛ рд╕реНрдерд╛рдирд╛рдиреНрддрд░рдгрдХреЛ рдХреБрдиреИ рд▓рд╛рдореЛ рдЪреЗрдирд╣рд░реВ рдЫреИрдирдиреН рднрдиреА рд╣рд╛рдореА рдорд╛рдиреНрдиреЗрдЫреМрдВред рддреНрдпреЛ рд╣реЛ, рддрд┐рдиреАрд╣рд░реВ рдЕрд╡рд╕реНрдерд┐рдд рдЫрдиреН, рддрд░ рддрд┐рдиреАрд╣рд░реВрдХреЛ рд▓рдореНрдмрд╛рдЗ рддреАрди рд╕рдиреНрджреЗрд╢рд╣рд░реВ рднрдиреНрджрд╛ рдмрдвреА рдЫреИрдиред рд╕рдиреНрджреЗрд╢рд╣рд░реВрдХреЛ рд╕рдореНрдкреВрд░реНрдг рд╢реНрд░реГрдВрдЦрд▓рд╛ рдлреНрд░рдиреНрдЯрдПрдиреНрдбрдорд╛ рдкрдард╛рдЗрдиреБ рдкрд░реНрдЫред

рдмрд╛рд╣реНрдп рд╕реЗрд╡рд╛рд╣рд░реВрдмрд╛рдЯ рдбрд╛рдЯрд╛ рдкреНрд░рд╛рдкреНрдд рдЧрд░реНрди рдирд┐рдореНрди API рд╣рд░реВ рдЫрдиреН:

interface ChatMessageRepository {
  fun findLast(nInt): List<ChatMessage>
}
data class FileHeadRemote(
  val id: FileReference,
  val name: String
)
interface FileRemoteApi {
  fun getHeadById(idFileReference): FileHeadRemote
  fun getHeadsByIds(idSet<FileReference>): Set<FileHeadRemote>
  fun getHeadsByIds(idList<FileReference>): List<FileHeadRemote>
  fun getHeadsByChat(): List<FileHeadRemote>
}
data class UserRemote(
  val id: UserReference,
  val name: String
)
interface UserRemoteApi {
  fun getUserById(idUserReference): UserRemote
  fun getUsersByIds(idSet<UserReference>): Set<UserRemote>
  fun getUsersByIds(idList<UserReference>): List<UserRemote>
}

рдпреЛ рджреЗрдЦреНрди рд╕рдХрд┐рдиреНрдЫ рдХрд┐ рдмрд╛рд╣реНрдп рд╕реЗрд╡рд╛рд╣рд░реВрд▓реЗ рд╕реБрд░реБрдорд╛ рдмреНрдпрд╛рдЪ рдкреНрд░рд╢реЛрдзрдирдХрд╛ рд▓рд╛рдЧрд┐ рдкреНрд░рджрд╛рди рдЧрд░реНрджрдЫ, рд░ рджреБрд╡реИ рд╕рдВрд╕реНрдХрд░рдгрд╣рд░реВрдорд╛: рд╕реЗрдЯ рдорд╛рд░реНрдлрдд (рддрддреНрд╡рд╣рд░реВрдХреЛ рдХреНрд░рдо рд╕реБрд░рдХреНрд╖рд┐рдд рдирдЧрд░реА, рдЕрджреНрд╡рд┐рддреАрдп рдХреБрдЮреНрдЬреАрд╣рд░реВрд╕рдБрдЧ) рд░ рд╕реВрдЪреА рдорд╛рд░реНрдлрдд (рддреНрдпрд╣рд╛рдБ рдбреБрдкреНрд▓рд┐рдХреЗрдЯ рд╣реБрди рд╕рдХреНрдЫ - рдЕрд░реНрдбрд░ рд╕реБрд░рдХреНрд╖рд┐рдд рдЫ)ред

рд╕рд░рд▓ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди

рдирд┐рд╖реНрдХрдкрдЯ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди

рд╣рд╛рдореНрд░реЛ REST рдирд┐рдпрдиреНрддреНрд░рдХрдХреЛ рдкрд╣рд┐рд▓реЛ рд╕рд░рд▓ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдзреЗрд░реИ рдЬрд╕реЛ рдХреЗрд╕рд╣рд░реВрдорд╛ рдпрд╕реНрддреЛ рджреЗрдЦрд┐рдиреНрдЫ:

class ChatRestController(
  private val messageRepository: ChatMessageRepository,
  private val userRepository: UserRemoteApi,
  private val fileRepository: FileRemoteApi
) : ChatRestApi {
  override fun getLast(nInt) =
    messageRepository.findLast(n)
      .map it.toFrontModel() }
  
  private fun ChatMessage.toFrontModel(): ChatMessageUI =
    ChatMessageUI(
      id = id ?: throw IllegalStateException("$this must be persisted"),
      author = userRepository.getUserById(author).toFrontReference(),
      message = message,
      files = files?.let files ->
        fileRepository.getHeadsByIds(files)
          .map it.toFrontReference() }
      } ?: listOf(),
      forwardFrom = forwardFrom?.toFrontModel(),
      replyTo = replyTo?.toFrontModel()
    )
}

рд╕рдмреИ рдХреБрд░рд╛ рдзреЗрд░реИ рд╕реНрдкрд╖реНрдЯ рдЫ, рд░ рдпреЛ рдПрдХ рдареВрд▓реЛ рдкреНрд▓рд╕ рд╣реЛред

рд╣рд╛рдореА рдмреНрдпрд╛рдЪ рдкреНрд░рд╢реЛрдзрди рдкреНрд░рдпреЛрдЧ рдЧрд░реНрдЫреМрдВ рд░ рдмреНрдпрд╛рдЪрд╣рд░реВрдорд╛ рдмрд╛рд╣реНрдп рд╕реЗрд╡рд╛рдмрд╛рдЯ рдбреЗрдЯрд╛ рдкреНрд░рд╛рдкреНрдд рдЧрд░реНрдЫреМрдВред рддрд░ рд╣рд╛рдореНрд░реЛ рдЙрддреНрдкрд╛рджрдХрддреНрд╡рдорд╛ рдХреЗ рд╣реБрдиреНрдЫ?

рдкреНрд░рддреНрдпреЗрдХ рд╕рдиреНрджреЗрд╢рдХреЛ рд▓рд╛рдЧрд┐, рд▓реЗрдЦрдХ рдлрд┐рд▓реНрдбрдорд╛ рдбреЗрдЯрд╛ рдкреНрд░рд╛рдкреНрдд рдЧрд░реНрди UserRemoteApi рдорд╛ рдПрдХ рдХрд▓ рд░ рд╕рдмреИ рд╕рдВрд▓рдЧреНрди рдлрд╛рдЗрд▓рд╣рд░реВ рдкреНрд░рд╛рдкреНрдд рдЧрд░реНрди FileRemoteApi рдорд╛ рдПрдХ рдХрд▓ рдЧрд░рд┐рдиреЗрдЫред рддреНрдпреЛ рд╣реЛ рдЬрд╕реНрддреЛ рд▓рд╛рдЧреНрдЫред рдорд╛рдиреМрдВ рдХрд┐ ChatMessage рдХреЛ рд▓рд╛рдЧрд┐ ForwardFrom рд░ replyTo рдлрд┐рд▓реНрдбрд╣рд░реВ рдпрд╕рд░реА рдкреНрд░рд╛рдкреНрдд рдЧрд░рд┐рдиреНрдЫ рдХрд┐ рдпрд╕рдХрд╛ рд▓рд╛рдЧрд┐ рдЕрдирд╛рд╡рд╢реНрдпрдХ рдХрд▓рд╣рд░реВ рдЖрд╡рд╢реНрдпрдХ рдкрд░реНрджреИрдиред рддрд░ рддрд┐рдиреАрд╣рд░реВрд▓рд╛рдИ ChatMessageUI рдорд╛ рдкрд░рд┐рдгрдд рдЧрд░реНрдирд╛рд▓реЗ рдкреБрдирд░рд╛рд╡реГрддреНрддрд┐ рд╣реБрдиреЗрдЫ, рдЕрд░реНрдерд╛рддреН, рдХрд▓ рдХрд╛рдЙрдиреНрдЯрд░рд╣рд░реВ рдЙрд▓реНрд▓реЗрдЦрдиреАрдп рд░реВрдкрдорд╛ рдмрдвреНрди рд╕рдХреНрдЫред рд╣рд╛рдореАрд▓реЗ рдкрд╣рд┐рд▓реЗ рдЙрд▓реНрд▓реЗрдЦ рдЧрд░реЗрдЭреИрдВ, рдорд╛рдиреМрдВ рдХрд┐ рд╣рд╛рдореАрд╕рдБрдЧ рдзреЗрд░реИ рдиреЗрд╕реНрдЯрд┐рдЩ рдЫреИрди рд░ рдЪреЗрди рддреАрди рд╕рдиреНрджреЗрд╢рд╣рд░реВрдорд╛ рд╕реАрдорд┐рдд рдЫред

рдлрд▓рд╕реНрд╡рд░реВрдк, рд╣рд╛рдореАрд▓реЗ рдкреНрд░рддрд┐ рд╕рдиреНрджреЗрд╢ рдмрд╛рд╣реНрдп рд╕реЗрд╡рд╛рд╣рд░реВрдорд╛ рджреБрдИ рджреЗрдЦрд┐ рдЫ рдХрд▓рд╣рд░реВ рд░ рд╕рдиреНрджреЗрд╢рд╣рд░реВрдХреЛ рд╕рдореНрдкреВрд░реНрдг рдкреНрдпрд╛рдХреЗрдЬрдХреЛ рд▓рд╛рдЧрд┐ рдПрдХ JPA рдХрд▓ рдкреНрд░рд╛рдкреНрдд рдЧрд░реНрдиреЗрдЫреМрдВред рдХрд▓рд╣рд░реВрдХреЛ рдХреБрд▓ рд╕рдВрдЦреНрдпрд╛ 2*N+1 рджреЗрдЦрд┐ 6*N+1 рд╕рдореНрдо рдлрд░рдХ рд╣реБрдиреЗрдЫред рд╡рд╛рд╕реНрддрд╡рд┐рдХ рдПрдХрд╛рдЗрд╣рд░реВрдорд╛ рдпреЛ рдХрддрд┐ рдЫ? рдорд╛рдиреМрдВ рдХрд┐ рдпреЛ рдкреГрд╖реНрда рд░реЗрдиреНрдбрд░ рдЧрд░реНрди 20 рд╕рдиреНрджреЗрд╢рд╣рд░реВ рд▓рд┐рдиреНрдЫред рддрд┐рдиреАрд╣рд░реВрд▓рд╛рдИ рдкреНрд░рд╛рдкреНрдд рдЧрд░реНрди, рдпрд╕рд▓реЗ 4 s рджреЗрдЦрд┐ 10 рд╕реЗрдХреЗрдиреНрдб рд╕рдореНрдо рд▓рд┐рдиреЗрдЫред рднрдпрд╛рдирдХ! рдо рдпрд╕рд▓рд╛рдИ 500 ms рднрд┐рддреНрд░ рд░рд╛рдЦреНрди рдЪрд╛рд╣рдиреНрдЫреБред рд░ рддрд┐рдиреАрд╣рд░реВрд▓реЗ рдлреНрд░рдиреНрдЯрдПрдиреНрдбрдорд╛ рд╕рд┐рдорд▓реЗрд╕ рд╕реНрдХреНрд░реЛрд▓рд┐рдЩ рдмрдирд╛рдЙрдиреЗ рд╕рдкрдирд╛ рджреЗрдЦреЗрдХрд╛рд▓реЗ, рдпрд╕ рдЕрдиреНрддреНрдпрдмрд┐рдиреНрджреБрдХреЛ рд▓рд╛рдЧрд┐ рдкреНрд░рджрд░реНрд╢рди рдЖрд╡рд╢реНрдпрдХрддрд╛рд╣рд░реВ рджреЛрдмреНрдмрд░ рдЧрд░реНрди рд╕рдХрд┐рдиреНрдЫред

рдкреНрд░реЛ:

  1. рдХреЛрдб рд╕рдВрдХреНрд╖рд┐рдкреНрдд рд░ рд╕реНрд╡-рдбрдХреБрдореЗрдиреНрдЯрд┐рдЩ (рдПрдХ рд╕рдорд░реНрдерди рдЯреЛрд▓реАрдХреЛ рд╕рдкрдирд╛) рд╣реЛред
  2. рдХреЛрдб рд╕рд░рд▓ рдЫ, рддреНрдпрд╕реИрд▓реЗ рдЦреБрдЯреНрдЯрд╛рдорд╛ рдЖрдлреИрд▓рд╛рдИ рдЧреЛрд▓реА рдорд╛рд░реНрдиреЗ рд▓рдЧрднрдЧ рдХреБрдиреИ рдЕрд╡рд╕рд░рд╣рд░реВ рдЫреИрдирдиреНред
  3. рдмреНрдпрд╛рдЪ рдкреНрд░рд╢реЛрдзрди рдХреБрдиреИ рд╡рд┐рджреЗрд╢реА рдЬрд╕реНрддреЛ рджреЗрдЦрд┐рджреИрди рд░ рддрд░реНрдХрдорд╛ рд╕рдВрдЧрдард┐рдд рд░реВрдкрдорд╛ рдПрдХреАрдХреГрдд рд╣реБрдиреНрдЫред
  4. рддрд░реНрдХ рдкрд░рд┐рд╡рд░реНрддрдирд╣рд░реВ рд╕рдЬрд┐рд▓реИ рдмрдирд╛рдЗрдиреЗрдЫ рд░ рд╕реНрдерд╛рдиреАрдп рд╣реБрдиреЗрдЫред

рдорд╛рдЗрдирд╕:

рдзреЗрд░реИ рд╕рд╛рдиреЛ рдкреНрдпрд╛рдХреЗрдЯрдХреЛ рдХрд╛рд░рдг рднрдпрд╛рдирдХ рдкреНрд░рджрд░реНрд╢рдиред

рдпреЛ рджреГрд╖реНрдЯрд┐рдХреЛрдг рд╕рд╛рдзрд╛рд░рдг рд╕реЗрд╡рд╛рд╣рд░реВрдорд╛ рд╡рд╛ рдкреНрд░реЛрдЯреЛрдЯрд╛рдЗрдкрд╣рд░реВрдорд╛ рдкреНрд░рд╛рдпрдГ рджреЗрдЦреНрди рд╕рдХрд┐рдиреНрдЫред рдпрджрд┐ рдкрд░рд┐рд╡рд░реНрддрди рдЧрд░реНрдиреЗ рдЧрддрд┐ рдорд╣рддреНрддреНрд╡рдкреВрд░реНрдг рдЫ рднрдиреЗ, рдпреЛ рдкреНрд░рдгрд╛рд▓реАрд▓рд╛рдИ рдЬрдЯрд┐рд▓ рдмрдирд╛рдЙрди рд╕рд╛рдпрдж рд▓рд╛рдпрдХ рдЫреИрдиред рдПрдХреИ рд╕рдордпрдорд╛, рд╣рд╛рдореНрд░реЛ рдзреЗрд░реИ рд╕рд░рд▓ рд╕реЗрд╡рд╛рдХреЛ рд▓рд╛рдЧрд┐ рдкреНрд░рджрд░реНрд╢рди рднрдпрд╛рдирдХ рдЫ, рддреНрдпрд╕реИрд▓реЗ рдпреЛ рджреГрд╖реНрдЯрд┐рдХреЛрдгрдХреЛ рд▓рд╛рдЧреВ рд╣реБрдиреЗ рджрд╛рдпрд░рд╛ рдзреЗрд░реИ рд╕рд╛рдБрдШреБрд░реЛ рдЫред

рднреЛрд▓реА рд╕рдорд╛рдирд╛рдВрддрд░ рдкреНрд░рд╢реЛрдзрди

рддрдкрд╛рдИрдВ рд╕рдорд╛рдирд╛рдиреНрддрд░рдорд╛ рд╕рдмреИ рд╕рдиреНрджреЗрд╢рд╣рд░реВ рдкреНрд░рд╢реЛрдзрди рд╕реБрд░реБ рдЧрд░реНрди рд╕рдХреНрдиреБрд╣реБрдиреНрдЫ - рдпрд╕рд▓реЗ рддрдкрд╛рдИрдВрд▓рд╛рдИ рд╕рдиреНрджреЗрд╢рд╣рд░реВрдХреЛ рд╕рдВрдЦреНрдпрд╛рдХреЛ рдЖрдзрд╛рд░рдорд╛ рд╕рдордпрдХреЛ рд░реИрдЦрд┐рдХ рд╡реГрджреНрдзрд┐рдмрд╛рдЯ рдЫреБрдЯрдХрд╛рд░рд╛ рдкрд╛рдЙрди рдЕрдиреБрдорддрд┐ рджрд┐рдиреЗрдЫред рдпреЛ рд╡рд┐рд╢реЗрд╖ рд░реВрдкрдорд╛ рд░рд╛рдореНрд░реЛ рдорд╛рд░реНрдЧ рд╣реЛрдЗрди рдХрд┐рдирднрдиреЗ рдпрд╕рд▓реЗ рдмрд╛рд╣реНрдп рд╕реЗрд╡рд╛рдорд╛ рдареВрд▓реЛ рд╢рд┐рдЦрд░ рднрд╛рд░рдХреЛ рдкрд░рд┐рдгрд╛рдо рджрд┐рдиреНрдЫред

рд╕рдорд╛рдирд╛рдиреНрддрд░ рдкреНрд░рд╢реЛрдзрди рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдзреЗрд░реИ рд╕рд░рд▓ рдЫ:

override fun getLast(nInt) =
  messageRepository.findLast(n).parallelStream()
    .map it.toFrontModel() }
    .collect(toList())

рд╕рдорд╛рдирд╛рдиреНрддрд░ рд╕рдиреНрджреЗрд╢ рдкреНрд░рд╢реЛрдзрди рдкреНрд░рдпреЛрдЧ рдЧрд░реЗрд░, рд╣рд╛рдореАрд▓реЗ рдЖрджрд░реНрд╢ рд░реВрдкрдорд╛ 300-700 ms рдкрд╛рдЙрдБрдЫреМрдВ, рдЬреБрди рд╕рд░рд▓ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрдирдХреЛ рддреБрд▓рдирд╛рдорд╛ рдзреЗрд░реИ рд░рд╛рдореНрд░реЛ рдЫ, рддрд░ рдЕрдЭреИ рдкрдирд┐ рдкрд░реНрдпрд╛рдкреНрдд рдЫрд┐рдЯреЛ рдЫреИрдиред

рдпрд╕ рджреГрд╖реНрдЯрд┐рдХреЛрдгрдХреЛ рд╕рд╛рде, рдкреНрд░рдпреЛрдЧрдХрд░реНрддрд╛ рд░рд┐рдкреЛрдЬрд┐рдЯрд░реА рд░ рдлрд╛рдЗрд▓ рд░рд┐рдкреЛрдЬрд┐рдЯрд░реАрдорд╛ рдЕрдиреБрд░реЛрдзрд╣рд░реВ рд╕рд┐рдВрдХреНрд░реЛрдирд╕ рд░реВрдкрдорд╛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдЧрд░рд┐рдиреЗрдЫ, рдЬреБрди рдзреЗрд░реИ рдкреНрд░рднрд╛рд╡рдХрд╛рд░реА рдЫреИрдиред рдпрд╕рд▓рд╛рдИ рдареАрдХ рдЧрд░реНрди, рддрдкрд╛рдИрдВрд▓реЗ рдХрд▓ рддрд░реНрдХ рдзреЗрд░реИ рдзреЗрд░реИ рдкрд░рд┐рд╡рд░реНрддрди рдЧрд░реНрдиреБрдкрд░реНрдиреЗрдЫред рдЙрджрд╛рд╣рд░рдг рдХреЛ рд▓рд╛рдЧреА, CompletionStage рдорд╛рд░реНрдлрдд (рдЙрд░реНрдл Completable Future):

private fun ChatMessage.toFrontModel(): ChatMessageUI =
  CompletableFuture.supplyAsync {
    userRepository.getUserById(author).toFrontReference()
  }.thenCombine(
    files?.let {
      CompletableFuture.supplyAsync {
        fileRepository.getHeadsByIds(files).map it.toFrontReference() }
      }
    } ?: CompletableFuture.completedFuture(listOf())
  ) authorfiles ->
    ChatMessageUI(
      id = id ?: throw IllegalStateException("$this must be persisted"),
      author = author,
      message = message,
      files = files,
      forwardFrom = forwardFrom?.toFrontModel(),
      replyTo = replyTo?.toFrontModel()
    )
  }.get()!!

рдпреЛ рджреЗрдЦреНрди рд╕рдХрд┐рдиреНрдЫ рдХрд┐ рдкреНрд░рд╛рд░рдореНрднрд┐рдХ рд╕рд░рд▓ рдореНрдпрд╛рдкрд┐рдЩ рдХреЛрдб рдХрдо рдмреБрдЭреНрди рдпреЛрдЧреНрдп рднрдПрдХреЛ рдЫред рдпреЛ рдХрд┐рдирднрдиреЗ рд╣рд╛рдореАрд▓реЗ рдмрд╛рд╣рд┐рд░реА рд╕реЗрд╡рд╛рд╣рд░реВрдорд╛ рдХрд▓рд╣рд░реВ рдЕрд▓рдЧ рдЧрд░реНрдиреБрдкрд░реНтАНрдпреЛ рдЬрд╣рд╛рдБ рдкрд░рд┐рдгрд╛рдорд╣рд░реВ рдкреНрд░рдпреЛрдЧ рдЧрд░рд┐рдиреНрдЫред рдпреЛ рдЖрдлреИрдорд╛ рдирд░рд╛рдореНрд░реЛ рд╣реЛрдЗрдиред рддрд░ рдХрд▓рд╣рд░реВ рд╕рдВрдпреЛрдЬрди рдЧрд░реНрджрд╛ рдзреЗрд░реИ рд╕реБрд░реБрдЪрд┐рдкреВрд░реНрдг рджреЗрдЦрд┐рдБрджреИрди рд░ рдПрдХ рд╕рд╛рдорд╛рдиреНрдп рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛рд╢реАрд▓ "рдиреВрдбрд▓" рдЬрд╕реНрддреЛ рджреЗрдЦрд┐рдиреНрдЫред

рдпрджрд┐ рддрдкрд╛рдЗрдБ coroutines рдкреНрд░рдпреЛрдЧ рдЧрд░реНрдиреБрд╣реБрдиреНрдЫ рднрдиреЗ, рд╕рдмреИ рдХреБрд░рд╛ рдЕрдзрд┐рдХ рд╕рднреНрдп рджреЗрдЦрд┐рдиреЗрдЫ:

private fun ChatMessage.toFrontModel(): ChatMessageUI =
  join(
    userRepository.getUserById(author).toFrontReference() },
    files?.let fileRepository.getHeadsByIds(files)
      .map it.toFrontReference() } } ?: listOf() }
  ).let (author, files) ->
    ChatMessageUI(
      id = id ?: throw IllegalStateException("$this must be persisted"),
      author = author,
      message = message,
      files = files,
      forwardFrom = forwardFrom?.toFrontModel(),
      replyTo = replyTo?.toFrontModel()
    )
  }

рдХрд╣рд╛рдВ

fun <ABjoin(a: () -> Ab: () -> B) =
  runBlocking(IO{
    awaitAll(async a() }async b() })
  }.let {
    it[0as to it[1as B
  }

рд╕реИрджреНрдзрд╛рдиреНрддрд┐рдХ рд░реВрдкрдорд╛, рдпрд╕реНрддреЛ рд╕рдорд╛рдирд╛рдиреНрддрд░ рдкреНрд░рд╢реЛрдзрди рдкреНрд░рдпреЛрдЧ рдЧрд░реЗрд░, рд╣рд╛рдореАрд▓реЗ 200-400 ms рдкреНрд░рд╛рдкреНрдд рдЧрд░реНрдиреЗрдЫреМрдВ, рдЬреБрди рдкрд╣рд┐рд▓реЗ рдиреИ рд╣рд╛рдореНрд░реЛ рдЕрдкреЗрдХреНрд╖рд╛рдХреЛ рдирдЬрд┐рдХ рдЫред

рджреБрд░реНрднрд╛рдЧреНрдпрд╡рд╢, рдпрд╕реНрддреЛ рд░рд╛рдореНрд░реЛ рд╕рдорд╛рдирд╛рдиреНрддрд░ рдЕрд╡рд╕реНрдерд┐рдд рдЫреИрди, рд░ рддрд┐рд░реНрди рдХреЛ рд▓рд╛рдЧреА рдореВрд▓реНрдп рдПрдХрджрдо рдХреНрд░реВрд░ рдЫ: рдПрдХреИ рд╕рдордпрдорд╛ рдХрд╛рдо рдЧрд░реНрдиреЗ рдХреЗрд╣рд┐ рдкреНрд░рдпреЛрдЧрдХрд░реНрддрд╛рд╣рд░реБ рд╕рдВрдЧ, рдЕрдиреБрд░реЛрдз рдХреЛ рдПрдХ рдмреНрдпрд╛рд░реЗрдЬ рд╕реЗрд╡рд╛рд╣рд░реБ рдорд╛ рдкрд░реНрдиреЗрдЫ, рдЬрд╕рд░реА рдкрдирд┐ рд╕рдорд╛рдирд╛рдиреНрддрд░ рдорд╛ рдкреНрд░рд╢реЛрдзрди рдЧрд░рд┐рдиреЗ рдЫреИрди, рддреНрдпрд╕реИрд▓реЗ рд╣рд╛рдореА рд╣рд╛рдореНрд░реЛ рджреБрдЦреА 4 s рдорд╛ рдлрд░реНрдХрд┐рдиреЗрдЫред

рдпрд╕реНрддреЛ рд╕реЗрд╡рд╛ рдкреНрд░рдпреЛрдЧ рдЧрд░реНрджрд╛ рдореЗрд░реЛ рдкрд░рд┐рдгрд╛рдо 1300 рд╕рдиреНрджреЗрд╢рд╣рд░реВ рдкреНрд░рд╢реЛрдзрди рдЧрд░реНрди 1700-20 ms рд╣реЛред рдпреЛ рдкрд╣рд┐рд▓реЛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рднрдиреНрджрд╛ рдЫрд┐рдЯреЛ рдЫ, рддрд░ рдЕрдЭреИ рдкрдирд┐ рд╕рдорд╕реНрдпрд╛ рд╕рдорд╛рдзрд╛рди рдЧрд░реНрджреИрдиред

рд╕рдорд╛рдирд╛рдиреНрддрд░ рдкреНрд░рд╢реНрдирд╣рд░реВрдХреЛ рд╡реИрдХрд▓реНрдкрд┐рдХ рдкреНрд░рдпреЛрдЧрд╣рд░реВрддреЗрд╕реНрд░реЛ-рдкрдХреНрд╖ рд╕реЗрд╡рд╛рд╣рд░реВрд▓реЗ рдмреНрдпрд╛рдЪ рдкреНрд░рд╢реЛрдзрди рдкреНрд░рджрд╛рди рдЧрд░реНрджреИрди рднрдиреЗ рдХреЗ рд╣реБрдиреНрдЫ? рдЙрджрд╛рд╣рд░рдгрдХрд╛ рд▓рд╛рдЧрд┐, рддрдкрд╛рдЗрдБ рдЗрдиреНрдЯрд░рдлреЗрд╕ рд╡рд┐рдзрд┐рд╣рд░реВ рднрд┐рддреНрд░ рдмреНрдпрд╛рдЪ рдкреНрд░рд╢реЛрдзрди рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрдирдХреЛ рдЕрднрд╛рд╡ рд▓реБрдХрд╛рдЙрди рд╕рдХреНрдиреБрд╣реБрдиреНрдЫ:

interface UserRemoteApi {
  fun getUserById(idUserReference): UserRemote
  fun getUsersByIds(idSet<UserReference>): Set<UserRemote> =
    id.parallelStream()
      .map getUserById(it}.collect(toSet())
  fun getUsersByIds(idList<UserReference>): List<UserRemote> =
    id.parallelStream()
      .map getUserById(it}.collect(toList())
}

рдпрджрд┐ рддрдкрд╛рдЗрдБ рднрд╡рд┐рд╖реНрдпрдХрд╛ рд╕рдВрд╕реНрдХрд░рдгрд╣рд░реВрдорд╛ рдмреНрдпрд╛рдЪ рдкреНрд░рд╢реЛрдзрди рд╣реЗрд░реНрди рдЖрд╢рд╛ рдЧрд░реНрдиреБрд╣реБрдиреНрдЫ рднрдиреЗ рдпрд╕рд▓реЗ рдЕрд░реНрде рджрд┐рдиреНрдЫред
рдкреНрд░реЛ:

  1. рд╕рдиреНрджреЗрд╢рдорд╛ рдЖрдзрд╛рд░рд┐рдд рд╕рдорд╛рдирд╛рдиреНрддрд░ рдкреНрд░рд╢реЛрдзрдирд▓рд╛рдИ рд╕рдЬрд┐рд▓реИ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдЧрд░реНрдиреБрд╣реЛрд╕реНред
  2. рд░рд╛рдореНрд░реЛ рд╕реНрдХреЗрд▓реЗрдмрд┐рд▓рд┐рдЯреАред

Cons:

  1. рд╕рдорд╛рдирд╛рдиреНрддрд░ рд░реВрдкрдорд╛ рд╡рд┐рднрд┐рдиреНрди рд╕реЗрд╡рд╛рд╣рд░реВрдорд╛ рдЕрдиреБрд░реЛрдзрд╣рд░реВ рдкреНрд░рд╢реЛрдзрди рдЧрд░реНрджрд╛ рдпрд╕рдХреЛ рдкреНрд░рд╢реЛрдзрдирдмрд╛рдЯ рдбрд╛рдЯрд╛ рдЕрдзрд┐рдЧреНрд░рд╣рдг рдЕрд▓рдЧ рдЧрд░реНрди рдЖрд╡рд╢реНрдпрдХ рдЫред
  2. рддреЗрд╕реНрд░реЛ-рдкрдХреНрд╖ рд╕реЗрд╡рд╛рд╣рд░реВрдорд╛ рд▓реЛрдб рдмрдвреНрдпреЛред

рдпреЛ рджреЗрдЦреНрди рд╕рдХрд┐рдиреНрдЫ рдХрд┐ рд╡реНрдпрд╛рд╡рд╣рд╛рд░рд┐рдХрддрд╛ рдХреЛ рджрд╛рдпрд░рд╛ рд▓рдЧрднрдЧ рднреЛрд▓реА рджреГрд╖реНрдЯрд┐рдХреЛрдг рдХреЛ рдЬрд╕реНрддреИ рдЫред рдпрджрд┐ рддрдкрд╛рдЗрдБ рдЕрд░реВрдХреЛ рдирд┐рд░реНрджрдпреА рд╢реЛрд╖рдгрдХреЛ рдХрд╛рд░рдгрд▓реЗ рддрдкрд╛рдЗрдБрдХреЛ рд╕реЗрд╡рд╛рдХреЛ рдХрд╛рд░реНрдпрд╕рдореНрдкрд╛рджрди рдзреЗрд░реИ рдкрдЯрдХ рдмрдврд╛рдЙрди рдЪрд╛рд╣рдиреБрд╣реБрдиреНрдЫ рднрдиреЗ рд╕рдорд╛рдирд╛рдиреНрддрд░ рдЕрдиреБрд░реЛрдз рд╡рд┐рдзрд┐ рдкреНрд░рдпреЛрдЧ рдЧрд░реНрди рдпреЛ рдЕрд░реНрдердкреВрд░реНрдг рдЫред рд╣рд╛рдореНрд░реЛ рдЙрджрд╛рд╣рд░рдгрдорд╛, рдкреНрд░рджрд░реНрд╢рди 2,5 рдЧреБрдгрд╛ рдмрдвреНрдпреЛ, рддрд░ рдпреЛ рд╕реНрдкрд╖реНрдЯ рд░реВрдкрдорд╛ рдкрд░реНрдпрд╛рдкреНрдд рдЫреИрдиред

рдХреНрдпрд╛рд╕рд┐рдЩ

рддрдкрд╛рдИрд▓реЗ рдмрд╛рд╣реНрдп рд╕реЗрд╡рд╛рд╣рд░реВрдХреЛ рд▓рд╛рдЧрд┐ JPA рдХреЛ рднрд╛рд╡рдирд╛рдорд╛ рдХреНрдпрд╛рд╕рд┐рдЩ рдЧрд░реНрди рд╕рдХреНрдиреБрд╣реБрдиреНрдЫ, рддреНрдпреЛ рд╣реЛ, рдкреНрд░рд╛рдкреНрдд рд╡рд╕реНрддреБрд╣рд░реВрд▓рд╛рдИ рд╕рддреНрд░ рднрд┐рддреНрд░ рднрдгреНрдбрд╛рд░ рдЧрд░реНрдиреБрд╣реЛрд╕реН рддрд╛рдХрд┐ рддрд┐рдиреАрд╣рд░реВрд▓рд╛рдИ рдлреЗрд░рд┐ рдкреНрд░рд╛рдкреНрдд рдирд╣реЛрд╕реН (рдмреНрдпрд╛рдЪ рдкреНрд░рд╢реЛрдзрдирдХрд╛ рдХреНрд░рдордорд╛ рд╕рд╣рд┐рдд)ред рддрдкрд╛рдИрдВрд▓реЗ рддреНрдпрд╕реНрддрд╛ рдХреНрдпрд╛рд╕рд╣рд░реВ рдЖрдлреИрдВ рдмрдирд╛рдЙрди рд╕рдХреНрдиреБрд╣реБрдиреНрдЫ, рддрдкрд╛рдИрдВ рдпрд╕рдХреЛ @Cacheable рд╕рдБрдЧ Spring рдкреНрд░рдпреЛрдЧ рдЧрд░реНрди рд╕рдХреНрдиреБрд╣реБрдиреНрдЫ, рд╕рд╛рдереИ рддрдкрд╛рдИрдВ рд╕рдзреИрдВ EhCache рдЬрд╕реНрддреИ рдореНрдпрд╛рдиреБрдЕрд▓ рд░реВрдкрдорд╛ рддрдпрд╛рд░ рдХреНрдпрд╛рд╕ рдкреНрд░рдпреЛрдЧ рдЧрд░реНрди рд╕рдХреНрдиреБрд╣реБрдиреНрдЫред

рдПрдХ рд╕рд╛рдорд╛рдиреНрдп рд╕рдорд╕реНрдпрд╛ рдпреЛ рд╣реБрдиреЗрдЫ рдХрд┐ рдХреНрдпрд╛рд╕рд╣рд░реВ рдорд╛рддреНрд░ рдЙрдкрдпреЛрдЧреА рдЫрдиреН рдпрджрд┐ рддрд┐рдиреАрд╣рд░реВрд╕рдБрдЧ рд╣рд┐рдЯ рдЫред рд╣рд╛рдореНрд░реЛ рдЕрд╡рд╕реНрдерд╛рдорд╛, рд▓реЗрдЦрдХ рдлрд┐рд▓реНрдбрдорд╛ рд╣рд┐рдЯрд╣рд░реВ рдзреЗрд░реИ рд╕рдореНрднрд╛рд╡рд┐рдд рдЫрдиреН (рдорд╛рдиреМрдВ, 50%), рддрд░ рддреНрдпрд╣рд╛рдБ рдлрд╛рдЗрд▓рд╣рд░реВрдорд╛ рдХреБрдиреИ рдкрдирд┐ рд╣рд┐рдЯ рд╣реБрдиреЗрдЫреИрдиред рдпрд╕ рджреГрд╖реНрдЯрд┐рдХреЛрдгрд▓реЗ рдХреЗрд╣реА рд╕реБрдзрд╛рд░рд╣рд░реВ рдкреНрд░рджрд╛рди рдЧрд░реНрдиреЗрдЫ, рддрд░ рдпрд╕рд▓реЗ рдореМрд▓рд┐рдХ рд░реВрдкрдорд╛ рдкреНрд░рджрд░реНрд╢рди рдкрд░рд┐рд╡рд░реНрддрди рдЧрд░реНрджреИрди (рд░ рд╣рд╛рдореАрд▓рд╛рдИ рд╕рдлрд▓рддрд╛ рдЪрд╛рд╣рд┐рдиреНрдЫ)ред

рдЗрдиреНрдЯрд░рд╕реЗрд╕рди (рд▓рд╛рдореЛ) рдХреНрдпрд╛рд╕рд╣рд░реВрд▓рд╛рдИ рдЬрдЯрд┐рд▓ рдЕрдорд╛рдиреНрдпрддрд╛ рддрд░реНрдХ рдЪрд╛рд╣рд┐рдиреНрдЫред рд╕рд╛рдорд╛рдиреНрдпрддрдпрд╛, рдкрдЫрд┐ рддрдкрд╛рдИрдВ рдЗрдиреНрдЯрд░рд╕реЗрд╕рди рдХреНрдпрд╛рд╕рд╣рд░реВ рдкреНрд░рдпреЛрдЧ рдЧрд░реЗрд░ рдкреНрд░рджрд░реНрд╢рди рд╕рдорд╕реНрдпрд╛рд╣рд░реВ рд╕рдорд╛рдзрд╛рди рдЧрд░реНрди рддрд▓ рдЬрд╛рдиреБрд╣реБрдиреНрдЫ, рд░рд╛рдореНрд░реЛред

рдкреНрд░реЛ:

  1. рдХреЛрдб рдкрд░рд┐рд╡рд░реНрддрди рдирдЧрд░реА рдХреНрдпрд╛рд╕рд┐рдЩ рд▓рд╛рдЧреВ рдЧрд░реНрдиреБрд╣реЛрд╕реНред
  2. рдЙрддреНрдкрд╛рджрдХрддрд╛ рдзреЗрд░реИ рдкрдЯрдХ рдмрдвреНрдпреЛ (рдХреЗрд╣рд┐ рдЕрд╡рд╕реНрдерд╛рдорд╛)ред

Cons:

  1. рдпрджрд┐ рдЧрд▓рдд рд░реВрдкрдорд╛ рдкреНрд░рдпреЛрдЧ рдЧрд░рд┐рдпреЛ рднрдиреЗ рдХрдо рдкреНрд░рджрд░реНрд╢рдирдХреЛ рд╕рдореНрднрд╛рд╡рдирд╛ред
  2. рдареВрд▓реЛ рдореЗрдореЛрд░реА рдУрднрд░рд╣реЗрдб, рд╡рд┐рд╢реЗрд╖ рдЧрд░реА рд▓рд╛рдореЛ рдХреНрдпрд╛рд╕ рд╕рдВрдЧред
  3. рдЬрдЯрд┐рд▓ рдЕрдорд╛рдиреНрдпрддрд╛, рддреНрд░реБрдЯрд┐рд╣рд░реВ рдЬрд╕рдорд╛ рд░рдирдЯрд╛рдЗрдордорд╛ рдкреБрди: рдЙрддреНрдкрд╛рджрди рдЧрд░реНрди рдЧрд╛рд╣реНрд░реЛ рд╣реБрдиреЗ рд╕рдорд╕реНрдпрд╛рд╣рд░реВ рд╣реБрдиреНрдЫрдиреНред

рдзреЗрд░реИ рдкрдЯрдХ, рдХреНрдпрд╛рд╕рд╣рд░реВ рдорд╛рддреНрд░ рдбрд┐рдЬрд╛рдЗрди рд╕рдорд╕реНрдпрд╛рд╣рд░реВ рдЫрд┐рдЯреЛ рдкреНрдпрд╛рдЪ рдЧрд░реНрди рдкреНрд░рдпреЛрдЧ рдЧрд░рд┐рдиреНрдЫред рдпрд╕рдХреЛ рдорддрд▓рдм рдпреЛ рд╣реЛрдЗрди рдХрд┐ рддрд┐рдиреАрд╣рд░реВ рдкреНрд░рдпреЛрдЧ рдЧрд░реНрдиреБ рд╣реБрдБрджреИрдиред рдЬреЗ рд╣реЛрд╕реН, рддрдкрд╛рдИрдВрд▓реЗ рддрд┐рдиреАрд╣рд░реВрд▓рд╛рдИ рд╕рдзреИрдВ рд╕рд╛рд╡рдзрд╛рдиреАрдкреВрд░реНрд╡рдХ рд╡реНрдпрд╡рд╣рд╛рд░ рдЧрд░реНрдиреБрдкрд░реНрдЫ рд░ рдкрд╣рд┐рд▓реЗ рдирддрд┐рдЬрд╛ рдкреНрд░рджрд░реНрд╢рди рд▓рд╛рднрдХреЛ рдореВрд▓реНрдпрд╛рдЩреНрдХрди рдЧрд░реНрдиреБрд╣реЛрд╕реН, рд░ рддреНрдпрд╕рдкрдЫрд┐ рдорд╛рддреНрд░ рдирд┐рд░реНрдгрдп рдЧрд░реНрдиреБрд╣реЛрд╕реНред

рд╣рд╛рдореНрд░реЛ рдЙрджрд╛рд╣рд░рдгрдорд╛, рдХреНрдпрд╛рд╕рд╣рд░реВрд▓реЗ рд▓рдЧрднрдЧ 25% рдХреЛ рдкреНрд░рджрд░реНрд╢рди рд╡реГрджреНрдзрд┐ рдкреНрд░рджрд╛рди рдЧрд░реНрдиреЗрдЫред рдПрдХреИ рд╕рдордпрдорд╛, рдХреНрдпрд╛рд╕рд╣рд░реВрдорд╛ рдзреЗрд░реИ рдмреЗрдлрд╛рдЗрджрд╛рд╣рд░реВ рдЫрдиреН, рддреНрдпрд╕реИрд▓реЗ рдо рддрд┐рдиреАрд╣рд░реВрд▓рд╛рдИ рдпрд╣рд╛рдБ рдкреНрд░рдпреЛрдЧ рдЧрд░реНрджрд┐рдиред

рдкрд░рд┐рдгрд╛рдорд╣рд░реВ

рддреНрдпрд╕реЛрднрдП, рд╣рд╛рдореАрд▓реЗ рдмреНрдпрд╛рдЪ рдкреНрд░рд╢реЛрдзрди рдкреНрд░рдпреЛрдЧ рдЧрд░реНрдиреЗ рд╕реЗрд╡рд╛рдХреЛ рдирд┐рд╖реНрдХрдкрдЯ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди, рд░ рдпрд╕рд▓рд╛рдИ рдЧрддрд┐ рдмрдврд╛рдЙрди рдХреЗрд╣реА рд╕рд░рд▓ рддрд░рд┐рдХрд╛рд╣рд░реВ рд╣реЗрд░реНрдпреМрдВред

рдпреА рд╕рдмреИ рд╡рд┐рдзрд┐рд╣рд░реВрдХреЛ рдореБрдЦреНрдп рдлрд╛рдЗрджрд╛ рд╕рд╛рджрдЧреА рд╣реЛ, рдЬрд╕рдмрд╛рдЯ рддреНрдпрд╣рд╛рдБ рдзреЗрд░реИ рд╕реБрдЦрдж рдкрд░рд┐рдгрд╛рдорд╣рд░реВ рдЫрдиреНред

рдпреА рд╡рд┐рдзрд┐рд╣рд░реВрд╕рдБрдЧ рдПрдХ рд╕рд╛рдорд╛рдиреНрдп рд╕рдорд╕реНрдпрд╛ рдЦрд░рд╛рдм рдкреНрд░рджрд░реНрд╢рди рд╣реЛ, рдореБрдЦреНрдп рд░реВрдкрдорд╛ рдкреНрдпрд╛рдХреЗрдЯрд╣рд░реВрдХреЛ рдЖрдХрд╛рд░рдХреЛ рдХрд╛рд░рдгрд▓реЗред рддреНрдпрд╕рдХрд╛рд░рдг, рдпрджрд┐ рдпреА рд╕рдорд╛рдзрд╛рдирд╣рд░реВрд▓реЗ рддрдкрд╛рдИрдВрд▓рд╛рдИ рдЙрдкрдпреБрдХреНрдд рдЧрд░реНрджреИрдирдиреН рднрдиреЗ, рдпреЛ рдердк рдХрдЯреНрдЯрд░рдкрдиреНрдереА рд╡рд┐рдзрд┐рд╣рд░реВ рд╡рд┐рдЪрд╛рд░ рдЧрд░реНрди рд▓рд╛рдпрдХ рдЫред

рддреНрдпрд╣рд╛рдБ рджреБрдИ рдореБрдЦреНрдп рджрд┐рд╢рд╛рд╣рд░реВ рдЫрдиреН рдЬрд╕рдорд╛ рддрдкрд╛рдЗрдБ рд╕рдорд╛рдзрд╛рдирд╣рд░реВ рдЦреЛрдЬреНрди рд╕рдХреНрдиреБрд╣реБрдиреНрдЫ:

  • рдбреЗрдЯрд╛рдХреЛ рд╕рд╛рде рдПрд╕рд┐рдиреНрдХреНрд░реЛрдирд╕ рдХрд╛рд░реНрдп (рдПрдХ рдкреНрд░рддрд┐рдорд╛рди рдкрд░рд┐рд╡рд░реНрддрди рдЖрд╡рд╢реНрдпрдХ рдЫ, рддреНрдпрд╕реИрд▓реЗ рдпрд╕ рд▓реЗрдЦрдорд╛ рдЫрд▓рдлрд▓ рдЧрд░рд┐рдПрдХреЛ рдЫреИрди);
  • рд╕рд┐рдВрдХреНрд░реЛрдирд╕ рдкреНрд░рд╢реЛрдзрди рдХрд╛рдпрдо рдЧрд░реНрджрд╛ рдмреНрдпрд╛рдЪрд╣рд░реВрдХреЛ рд╡рд┐рд╕реНрддрд╛рд░ред

рдмреНрдпрд╛рдЪрд╣рд░реВрдХреЛ рд╡рд┐рд╕реНрддрд╛рд░рд▓реЗ рдмрд╛рд╣реНрдп рдХрд▓рд╣рд░реВрдХреЛ рд╕рдВрдЦреНрдпрд╛рд▓рд╛рдИ рдзреЗрд░реИ рдХрдо рдЧрд░реНрдиреЗрдЫ рд░ рдПрдХреИ рд╕рдордпрдорд╛ рдХреЛрдбрд▓рд╛рдИ рд╕рд┐рдВрдХреНрд░реЛрдирд╕ рд░рд╛рдЦреНрдЫред рд▓реЗрдЦрдХреЛ рдЕрд░реНрдХреЛ рднрд╛рдЧ рдпрд╕ рд╡рд┐рд╖рдпрдорд╛ рд╕рдорд░реНрдкрд┐рдд рд╣реБрдиреЗрдЫред

рд╕реНрд░реЛрдд: www.habr.com

рдПрдХ рдЯрд┐рдкреНрдкрдгреА рдердкреНрди