рдорд╛рдЗрдХреНрд░реЛрдиреЙрдЯ рдпрд╛ рдбрд╛рд░реНрд▓рд┐рдВрдЧ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░реЛ, рдореИрдВрдиреЗ рд░реВрдкрд░реЗрдЦрд╛ рдХреЛ рдХрдо рдХрд░ рджрд┐рдпрд╛

рдорд╛рдЗрдХреНрд░реЛрдиреЙрдЯ рдпрд╛ рдбрд╛рд░реНрд▓рд┐рдВрдЧ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░реЛ, рдореИрдВрдиреЗ рд░реВрдкрд░реЗрдЦрд╛ рдХреЛ рдХрдо рдХрд░ рджрд┐рдпрд╛


рдорд╛рдЗрдХреНрд░реЛрдиреЙрдЯ рдлреНрд░реЗрдорд╡рд░реНрдХ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ, рдореИрдВрдиреЗ рдиреНрдпреВрдЬрд▓реЗрдЯрд░ рдбрд╛рдЗрдЬреЗрд╕реНрдЯ рдХреА рдПрдХ рдЭрд▓рдХ рдкрдХрдбрд╝реАред рд╡рд╣ рд╕реЛрдЪрддрд╛ рдерд╛ рдХрд┐ рд╡рд╣ рдХреИрд╕рд╛ рдЬрд╛рдирд╡рд░ рдерд╛ред рд░реВрдкрд░реЗрдЦрд╛ рдХреЛ рд╕рднреА рдЖрд╡рд╢реНрдпрдХ рдЙрдкрдХрд░рдгреЛрдВ рдХреЗ рд╕рд╛рде рднрд░рд╡рд╛рдВ рд╡рд╕рдВрдд рдХреЗ рд╡рд┐рдкрд░реАрдд рд░рдЦрд╛ рдЧрдпрд╛ рд╣реИред


Micronaut


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


рдорд╛рдЗрдХреНрд░реЛрдиреЙрдЯ рдПрдХ рдЬреЗрд╡реАрдПрдо рдлреНрд░реЗрдорд╡рд░реНрдХ рд╣реИ рдЬреЛ рддреАрди рд╡рд┐рдХрд╛рд╕ рднрд╛рд╖рд╛рдУрдВ рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рддрд╛ рд╣реИ: рдЬрд╛рд╡рд╛, рдХреЛрдЯрд▓рд┐рди, рдЧреНрд░реВрд╡реАред рдпрд╣ OCI рджреНрд╡рд╛рд░рд╛ рд╡рд┐рдХрд╕рд┐рдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛, рд╡рд╣реА рдХрдВрдкрдиреА рдЬреЛ рдЧреНрд░рд┐рд▓реНрд╕ рдиреЗ рд╣рдореЗрдВ рджреА рдереАред рдЗрд╕рдореЗрдВ рдПрдХ cli рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдФрд░ рдЕрдиреБрд╢рдВрд╕рд┐рдд рдкреБрд╕реНрддрдХрд╛рд▓рдпреЛрдВ (рд╡рд┐рднрд┐рдиреНрди рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛рд╢реАрд▓-http рдФрд░ рдбреЗрдЯрд╛рдмреЗрд╕ рдХреНрд▓рд╛рдЗрдВрдЯ) рдХреЗ рд╕реЗрдЯ рдХреЗ рд░реВрдк рдореЗрдВ рдЯреНрдпреВрдирд┐рдВрдЧ рд╣реИред

рдПрдХ DI рд╣реИ рдЬреЛ рд╕реНрдкреНрд░рд┐рдВрдЧ рдХреЗ рд╡рд┐рдЪрд╛рд░реЛрдВ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рддрд╛ рд╣реИ рдФрд░ рджреЛрд╣рд░рд╛рддрд╛ рд╣реИ, рдЗрд╕рдХреЗ рдЪрд┐рдкреНрд╕ рдХреЛ рдЬреЛрдбрд╝ рд░рд╣рд╛ рд╣реИ - рдЕрддреБрд▓реНрдпрдХрд╛рд▓рд┐рдХрддрд╛, AWS рд▓реИрдореНрдмреНрдбрд╛ рдХреЗ рд▓рд┐рдП рд╕рдорд░реНрдерди, рдХреНрд▓рд╛рдЗрдВрдЯ рд╕рд╛рдЗрдб рд▓реЛрдб рдмреИрд▓реЗрдВрд╕рд┐рдВрдЧред

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


рдЕрд╕реНрд╡реАрдХрд░рдг


  1. рд╣рдо рдЕрдкрдиреЗ рд╕реНрд╡рдпрдВ рдХреЗ рд╕рдорд╛рдзрд╛рди рдХреЛ рдУрд╡рд░рдмреЛрд░реНрдб рд▓рд┐рдЦрдиреЗ рдХреА рд╢реАрдШреНрд░рддрд╛ рдХреЛ рдЫреЛрдбрд╝ рджреЗрдВрдЧреЗ, рд╣рдореЗрдВ рдмрд╕ рд╣реИрд▓реЛрд╡рд░реНрд▓реНрдб рдХреА рддреБрд▓рдирд╛ рдореЗрдВ рдХреБрдЫ рдЕрдзрд┐рдХ рдЪрд╛рд▓рд╛рдХ рдкрд░ рдирдП рдврд╛рдВрдЪреЗ рдХреЛ рдЖрдЬрд╝рдорд╛рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред
  2. рдЧрдгрдирд╛ рдПрд▓реНрдЧреЛрд░рд┐рдереНрдо, рдЕрдкреЗрдХреНрд╖рд┐рдд рддреНрд░реБрдЯрд┐рдпрд╛рдВ, рддреНрд░реБрдЯрд┐рдпрд╛рдВ, рдЖрджрд┐ред (рдХрдо рд╕реЗ рдХрдо рдЙрддреНрдкрд╛рдж рдХреЗ рдкрд╣рд▓реЗ рдЪрд░рдг рдХреЗ рд▓рд┐рдП), рдЬрд╛рдирдХрд╛рд░реА рдЦреАрдВрдЪрдиреЗ рдХреЗ рд▓рд┐рдП рдХреНрд░рд┐рдкреНрдЯреЛ рдПрдХреНрд╕рдЪреЗрдВрдЬреЛрдВ рдХреА рдкрд╕рдВрдж рдХреА рд╡реИрдзрддрд╛, рдПрдХ рджреЛрд╕реНрдд рдХрд╛ "рдирд┐рд╡реЗрд╢" рдХреНрд░рд┐рдкреНрдЯреЛ рдкреЛрд░реНрдЯрдлреЛрд▓рд┐рдпреЛ рднреА рд╕рд╡рд╛рд▓ рд╕реЗ рдмрд╛рд╣рд░ рд╣реЛрдЧрд╛ рдФрд░ рдЪрд░реНрдЪрд╛ рдпрд╛ рдХрд┐рд╕реА рддрд░рд╣ рдХреЗ рдЧрд╣рди рд╡рд┐рд╢реНрд▓реЗрд╖рдг рдХреЗ рдЕрдзреАрди рдирд╣реАрдВ рд╣реЛрдЧрд╛ред

рддреЛ, рдЖрд╡рд╢реНрдпрдХрддрд╛рдУрдВ рдХрд╛ рдПрдХ рдЫреЛрдЯрд╛ рд╕рд╛ рд╕реЗрдЯ:


  1. рд╡реЗрдм рд╕реЗрд╡рд╛ (http рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдмрд╛рд╣рд░ рд╕реЗ рдкрд╣реБрдВрдЪ)
  2. рдХреНрд░рд┐рдкреНрдЯреЛрдХреНрдпреВрд░реЗрдВрд╕реА рдкреЛрд░реНрдЯрдлреЛрд▓рд┐рдпреЛ рдХреЗ рдХреБрд▓ рдореВрд▓реНрдп рдХреЗ рд╕рд╛рд░рд╛рдВрд╢ рдХреЗ рд╕рд╛рде рдПрдХ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдореЗрдВ рдПрдХ рдкреГрд╖реНрда рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдирд╛
  3. рдкреЛрд░реНрдЯрдлреЛрд▓рд┐рдпреЛ рдХреЛ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд░рдиреЗ рдХреА рдХреНрд╖рдорддрд╛ (рдкреЛрд░реНрдЯрдлреЛрд▓рд┐рдпреЛ рд╕рдВрд░рдЪрдирд╛ рдХреЛ рд▓реЛрдб рдХрд░рдиреЗ рдФрд░ рдЙрддрд╛рд░рдиреЗ рдХреЗ рд▓рд┐рдП JSON рдкреНрд░рд╛рд░реВрдк рдЪреБрдиреЗрдВ)ред рдкреЛрд░реНрдЯрдлреЛрд▓рд┐рдпреЛ рдХреЛ рдЕрдкрдбреЗрдЯ рдХрд░рдиреЗ рдФрд░ рдЙрд╕реЗ рд▓реЛрдб рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдирд┐рд╢реНрдЪрд┐рдд REST API, рдЕрд░реНрдерд╛рдд 2 API: рд╕реЗрд╡рд┐рдВрдЧ / рдЕрдкрдбреЗрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП - POST, рдЕрдирд▓реЛрдбрд┐рдВрдЧ рдХреЗ рд▓рд┐рдП - GETред рдкреЛрд░реНрдЯрдлреЛрд▓рд┐рдпреЛ рд╕рдВрд░рдЪрдирд╛ рдЕрдирд┐рд╡рд╛рд░реНрдп рд░реВрдк рд╕реЗ рдПрдХ рд╕рд░рд▓ рдкреНрд░рдХрд╛рд░ рдХреА рдкреНрд▓реЗрдЯ рд╣реИ
    BTC тАУ  0.00005 . XEM тАУ  4.5 . ... 
  4. рд╣рдо рдХреНрд░рд┐рдкреНрдЯреЛрдХрд░рдВрд╕реА рдФрд░ рдореБрджреНрд░рд╛ рд╡рд┐рдирд┐рдордп рд╕реНрд░реЛрддреЛрдВ рд╕реЗ рдбреЗрдЯрд╛ рд▓реЗрддреЗ рд╣реИрдВ (рдлрд┐рдПрдЯ рдореБрджреНрд░рд╛рдУрдВ рдХреЗ рд▓рд┐рдП)
  5. рдкреЛрд░реНрдЯрдлреЛрд▓рд┐рдпреЛ рдХреЗ рдХреБрд▓ рдореВрд▓реНрдп рдХреА рдЧрдгрдирд╛ рдХреЗ рд▓рд┐рдП рдирд┐рдпрдо:
    рдкреЛрд░реНрдЯрдлреЛрд▓рд┐рдпреЛ рдХреЗ рдХреБрд▓ рдореВрд▓реНрдп рдХреА рдЧрдгрдирд╛ рдХреЗ рд▓рд┐рдП рд╕реВрддреНрд░


рдмреЗрд╢рдХ, рд╕рдм рдХреБрдЫ рдЬреЛ рдЕрдиреБрдЪреНрдЫреЗрдж 5 рдореЗрдВ рд▓рд┐рдЦрд╛ рдЧрдпрд╛ рд╣реИ, рдЕрд▓рдЧ-рдЕрд▓рдЧ рд╡рд┐рд╡рд╛рджреЛрдВ рдФрд░ рд╕рдВрджреЗрд╣ рдХрд╛ рд╡рд┐рд╖рдп рд╣реИ, рд▓реЗрдХрд┐рди рдпрд╣ рдмрддрд╛рдПрдВ рдХрд┐ рд╡реНрдпрд╡рд╕рд╛рдп рдРрд╕рд╛ рд╣реА рдЪрд╛рд╣рддрд╛ рдерд╛ред


рдкреНрд░реЛрдЬреЗрдХреНрдЯ рд╢реБрд░реВ


рдЗрд╕рд▓рд┐рдП, рд╣рдо рдврд╛рдВрдЪреЗ рдХреА рдЖрдзрд┐рдХрд╛рд░рд┐рдХ рд╡реЗрдмрд╕рд╛рдЗрдЯ рдкрд░ рдЬрд╛рддреЗ рд╣реИрдВ рдФрд░ рджреЗрдЦрддреЗ рд╣реИрдВ рдХрд┐ рд╣рдо рдХреИрд╕реЗ рд╡рд┐рдХрд╕рд┐рдд рдХрд░рдирд╛ рд╢реБрд░реВ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдЖрдзрд┐рдХрд╛рд░рд┐рдХ рд╕рд╛рдЗрдЯ sdkman рдЯреВрд▓ рдХреЛ рд╕реНрдерд╛рдкрд┐рдд рдХрд░рдиреЗ рдХреА рдкреЗрд╢рдХрд╢ рдХрд░рддреА рд╣реИред рдПрдХ рдЯреБрдХрдбрд╝рд╛ рдЬреЛ рдЕрдВрддрд░рд┐рдХреНрд╖ рдпрд╛рддреНрд░реА рдврд╛рдВрдЪреЗ рдкрд░ рдкрд░рд┐рдпреЛрдЬрдирд╛рдУрдВ рдХреЗ рд╡рд┐рдХрд╛рд╕ рдФрд░ рдкреНрд░рдмрдВрдзрди рдХреА рд╕реБрд╡рд┐рдзрд╛ рдкреНрд░рджрд╛рди рдХрд░рддрд╛ рд╣реИ (рдФрд░ рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдЧреНрд░реЗрд▓реНрд╕ рд╕рд╣рд┐рдд рдЕрдиреНрдп)ред


рд╡рд┐рднрд┐рдиреНрди рдПрд╕рдбреАрдХреЗ рдХреЗ рдПрдХ рд╣реА рдкреНрд░рдмрдВрдзрдХ

рдПрдХ рдЫреЛрдЯреА рд╕реА рдЯрд┐рдкреНрдкрдгреА: рдпрджрд┐ рдЖрдк рдмрд┐рдирд╛ рдХрд┐рд╕реА рдХреБрдВрдЬреА рдХреЗ рд╕рд┐рд░реНрдл рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдЗрдирд┐рд╢рд┐рдпрд▓рд╛рдЗрдЬрд╝реЗрд╢рди рд╢реБрд░реВ рдХрд░рддреЗ рд╣реИрдВ, рддреЛ рдЧреНрд░реЗрдбрд▓ рдХрд▓реЗрдХреНрдЯрд░ рдХреЛ рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд░реВрдк рд╕реЗ рдЪреБрдирд╛ рдЬрд╛рддрд╛ рд╣реИред рдлрд╝реЛрд▓реНрдбрд░ рд╣рдЯрд╛рдПрдВ, рдлрд┐рд░ рд╕реЗ рдкреНрд░рдпрд╛рд╕ рдХрд░реЗрдВ, рдЗрд╕ рдмрд╛рд░ рдХреБрдВрдЬреА рдХреЗ рд╕рд╛рде:
 mn create-app com.room606.cryptonaut -b=maven 

рдПрдХ рджрд┐рд▓рдЪрд╕реНрдк рдмрд┐рдВрджреБ рдпрд╣ рд╣реИ рдХрд┐ sdkman, рд╕реНрдкреНрд░рд┐рдВрдЧ рдЯреВрд▓ рд╕реВрдЯ рдХреА рддрд░рд╣, рдЖрдкрдХреЛ рдПрдХ рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдмрдирд╛рдиреЗ рдХреЗ рдЪрд░рдг рдореЗрдВ рдкреЗрд╢ рдХрд░рддрд╛ рд╣реИ, рдЬрд┐рд╕реЗ рдЖрдк "cubes" рд╢реБрд░реВ рдореЗрдВ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВред рдореИрдВрдиреЗ рдЗрд╕рдХреЗ рд╕рд╛рде рдЕрдзрд┐рдХ рдкреНрд░рдпреЛрдЧ рдирд╣реАрдВ рдХрд┐рдпрд╛, рдореИрдВрдиреЗ рдЗрд╕реЗ рдбрд┐рдлрд╝реЙрд▓реНрдЯ рдкреНрд░реАрд╕реЗрдЯ рдХреЗ рд╕рд╛рде рднреА рдмрдирд╛рдпрд╛ред


рдЕрдВрдд рдореЗрдВ, рд╣рдо рдЗрдВрдЯреЗрд▓реАрдЬ рдЖрдЗрдбрд┐рдпрд╛ рдореЗрдВ рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдХреЛ рдЦреЛрд▓рддреЗ рд╣реИрдВ рдФрд░ рд╕реНрд░реЛрддреЛрдВ рдФрд░ рд╕рдВрд╕рд╛рдзрдиреЛрдВ рдХреЗ рд╕реЗрдЯ рдХреА рдкреНрд░рд╢рдВрд╕рд╛ рдХрд░рддреЗ рд╣реИрдВ рдФрд░ рдбрд┐рд╕реНрдХ рдХреЛ рдорд╛рдЗрдХреНрд░реЛрдкреНрд░реЛрдЯреНрдЯ рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рд╣рдореЗрдВ рдкреНрд░рджрд╛рди рдХрд┐рдпрд╛ рдЧрдпрд╛ред


рдирдЧреНрди рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреА рд╕рдВрд░рдЪрдирд╛

рдбреЙрдХрд░реАрдлрд╛рдЗрд▓ рдХреЗ рд▓рд┐рдП рдЖрдВрдЦреЗрдВ
 FROM openjdk:8u171-alpine3.7 RUN apk --no-cache add curl COPY target/cryptonaut*.jar cryptonaut.jar CMD java ${JAVA_OPTS} -jar cryptonaut.jar 

рдЦреИрд░, рдпрд╣ рдордЬреЗрджрд╛рд░ рдФрд░ рд╕рд░рд╛рд╣рдиреАрдп рд╣реИред рд╣рдореЗрдВ рддреБрд░рдВрдд рдЙрддреНрдкрд╛рджрди / INT / QA / рдЬреЛ рднреА рдкрд░реНрдпрд╛рд╡рд░рдг рдХреЗ рд▓рд┐рдП рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЛ рдЖрдЙрдЯрдкреБрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдЙрдкрдХрд░рдг рдкреНрд░рджрд╛рди рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ред рдЗрд╕рдХреЗ рд▓рд┐рдП, рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЗ рд▓рд┐рдП рдПрдХ рдорд╛рдирд╕рд┐рдХ рдкреНрд▓рд╕ рд╕рдВрдХреЗрддред


рдпрд╣ рдорд╛рд╡реЗрди рджреНрд╡рд╛рд░рд╛ рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЛ рдЗрдХрдЯреНрдард╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдкрд░реНрдпрд╛рдкреНрдд рд╣реИ, рдлрд┐рд░ рдбреЙрдХрд░ рдЫрд╡рд┐ рдХреЛ рдЗрдХрдЯреНрдард╛ рдХрд░реЗрдВ рдФрд░ рдЗрд╕реЗ рдЕрдкрдиреА рдбреЙрдХрд░ рд░рдЬрд┐рд╕реНрдЯреНрд░реА рдореЗрдВ рдкреНрд░рдХрд╛рд╢рд┐рдд рдХрд░реЗрдВ, рдпрд╛ рдмрд╕ рдЫрд╡рд┐ рдХреЛ рдЕрдкрдиреЗ рд╕реАрдЖрдИ рд╕рд┐рд╕реНрдЯрдо рдХреЗ рд╡рд┐рдХрд▓реНрдк рдХреЗ рд░реВрдк рдореЗрдВ рдмрд╛рдЗрдирд░реА рдирд┐рд░реНрдпрд╛рдд рдХрд░реЗрдВ, рдпрд╣реА рдЖрдк рдЪрд╛рд╣рддреЗ рд╣реИрдВред


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


рд╣рдо рдЖрд╡реЗрджрди рдХреЗ рдкреНрд░рд╡реЗрд╢ рдмрд┐рдВрджреБ рдкрд░ рдЬрд╛рддреЗ рд╣реИрдВ рдФрд░ рдХрдХреНрд╖рд╛ рдХрд╛ рдЕрдзреНрдпрдпрди рдХрд░рддреЗ рд╣реИрдВред рд╣рдо рд╕реНрдкреНрд░рд┐рдВрдЧ рдмреВрдЯ рд╕реЗ рджрд░реНрдж рд╕реЗ рдкрд░рд┐рдЪрд┐рдд рдПрдХ рддрд╕реНрд╡реАрд░ рджреЗрдЦрддреЗ рд╣реИрдВред рдЗрдзрд░, рдврд╛рдВрдЪреЗ рдХреЗ рдбреЗрд╡рд▓рдкрд░реНрд╕ рдиреЗ рднреА рдХреБрдЫ рднреА рдЖрд╡рд┐рд╖реНрдХрд╛рд░ рдФрд░ рдЖрд╡рд┐рд╖реНрдХрд╛рд░ рдХрд░рдирд╛ рд╢реБрд░реВ рдирд╣реАрдВ рдХрд┐рдпрд╛ред


 public static void main(String[] args) throws IOException { Micronaut.run(Application.class); } 

рдкрд░рд┐рдЪрд┐рдд рд╕реНрдкреНрд░рд┐рдВрдЧ рдХреЛрдб рдХреЗ рд╕рд╛рде рддреБрд▓рдирд╛ рдХрд░реЗрдВред


 public static void main(String[] args) { SpringApplication.run(Application.class, args); } 

рдпрд╛рдиреА рд╣рдо рдЙрди рд╕рднреА рдлрд▓рд┐рдпреЛрдВ рдХреЗ рд╕рд╛рде IoC рдХрдВрдЯреЗрдирд░ рднреА рдЙрдард╛рддреЗ рд╣реИрдВ рдЬреЛ рдХрд╛рдо рдореЗрдВ рдЖрд╡рд╢реНрдпрдХ рд░реВрдк рд╕реЗ рд╢рд╛рдорд┐рд▓ рд╣реИрдВред рдЖрдзрд┐рдХрд╛рд░рд┐рдХ рджрд╕реНрддрд╛рд╡реЗрдЬ рдХреЗ рдЕрдиреБрд╕рд╛рд░ рдереЛрдбрд╝рд╛ рдЪрд▓рдиреЗ рдХреЗ рдмрд╛рдж, рд╣рдо рдзреАрд░реЗ-рдзреАрд░реЗ рд╡рд┐рдХрд╛рд╕ рд╢реБрд░реВ рдХрд░рддреЗ рд╣реИрдВред


рд╣рдореЗрдВ рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрдЧреА:


  1. рдбреЛрдореЗрди рдореЙрдбрд▓
  2. REST API рдХреЛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдирд┐рдпрдВрддреНрд░рдХред
  3. рдбреЗрдЯрд╛ рд╕рдВрдЧреНрд░рд╣рдг рдкрд░рдд (рдбреЗрдЯрд╛рдмреЗрд╕ рдХреНрд▓рд╛рдЗрдВрдЯ рдпрд╛ ORM рдпрд╛ рдХреБрдЫ рдФрд░)
  4. рдХреНрд░рд┐рдкреНрдЯреЛрдХреНрдпреВрд░реЗрдВрд╕реА рдПрдХреНрд╕рдЪреЗрдВрдЬреЛрдВ рд╕реЗ рдбреЗрдЯрд╛ рдХреЗ рдЙрдкрднреЛрдХреНрддрд╛рдУрдВ рдХрд╛ рдПрдХ рдХреЛрдб, рд╕рд╛рде рд╣реА рд╕рд╛рде рдлрд┐рдПрдЯ рдореБрджреНрд░рд╛ рд╡рд┐рдирд┐рдордп рд╕реЗ рдбреЗрдЯрд╛ред рдпрд╛рдиреА рд╣рдореЗрдВ 3rd рдкрд╛рд░реНрдЯреА рд╕реЗрд╡рд╛рдУрдВ рдХреЗ рд▓рд┐рдП рд╕рдмрд╕реЗ рд╕рд░рд▓ рдХреНрд▓рд╛рдЗрдВрдЯ рд▓рд┐рдЦрдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред рд╡рд╕рдВрдд рдореЗрдВ, рдкреНрд░рд╕рд┐рджреНрдз рд░реЗрд╕реНрдЯреЗрдордкреНрд▓реЗрдЯ рдиреЗ рдЗрд╕ рднреВрдорд┐рдХрд╛ рдХреЛ рдЕрдЪреНрдЫреА рддрд░рд╣ рд╕реЗ рдЕрдиреБрдХреВрд▓ рдХрд┐рдпрд╛ред
  5. рд▓рдЪреАрд▓реЗ рдкреНрд░рдмрдВрдзрди рдФрд░ рдЕрдиреБрдкреНрд░рдпреЛрдЧ рдкреНрд░рд╛рд░рдВрдн рдХреЗ рд▓рд┐рдП рдиреНрдпреВрдирддрдо рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди (рдЖрдЗрдП рд╡рд┐рдЪрд╛рд░ рдХрд░реЗрдВ рдХрд┐ рд╣рдо рдХреНрдпрд╛ рдФрд░ рдХреИрд╕реЗ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рдХрд░реЗрдВрдЧреЗ)
  6. рдЯреЗрд╕реНрдЯ! рд╣рд╛рдВ, рдЖрддреНрдорд╡рд┐рд╢реНрд╡рд╛рд╕ рд╕реЗ рдФрд░ рд╕реБрд░рдХреНрд╖рд┐рдд рд░реВрдк рд╕реЗ рдХреЛрдб рдХреЛ рд░реАрдЪрд╛рд░реНрдЬ рдХрд░рдиреЗ рдФрд░ рдирдИ рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдореЗрдВ рдкреБрд░рд╛рдиреЗ рдХреА рд╕реНрдерд┐рд░рддрд╛ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП
  7. рднрдВрдбрд╛рд░рд┐рдд рдХрд░рддрд╛ рд╣реИред рдпрд╣ рдПрдХ рдмреБрдирд┐рдпрд╛рджреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╣реАрдВ рд╣реИ, рд▓реЗрдХрд┐рди рдРрд╕рд╛ рдХреБрдЫ рд╣реИ рдЬреЛ рдЕрдЪреНрдЫреЗ рдкреНрд░рджрд░реНрд╢рди рдХреЗ рд▓рд┐рдП рдЕрдЪреНрдЫрд╛ рд╣реЛрдЧрд╛, рдФрд░ рд╣рдорд╛рд░реЗ рдкрд░рд┐рджреГрд╢реНрдп рдореЗрдВ рдРрд╕реЗ рд╕реНрдерд╛рди рд╣реИрдВ рдЬрд╣рд╛рдВ рдХреИрд╢рд┐рдВрдЧ рдирд┐рд╢реНрдЪрд┐рдд рд░реВрдк рд╕реЗ рдПрдХ рдЕрдЪреНрдЫрд╛ рдЙрдкрдХрд░рдг рд╣реИред
    Spoiler: рдпрд╣рд╛рдВ рд╕рдм рдХреБрдЫ рдмрд╣реБрдд рдЦрд░рд╛рдм рд╣реЛ рдЬрд╛рдПрдЧрд╛ред

рдбреЛрдореЗрди рдореЙрдбрд▓


рд╣рдорд╛рд░реЗ рдЙрджреНрджреЗрд╢реНрдпреЛрдВ рдХреЗ рд▓рд┐рдП, рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдореЙрдбрд▓ рдкрд░реНрдпрд╛рдкреНрдд рд╣реЛрдВрдЧреЗ: рдПрдХ рдХреНрд░рд┐рдкреНрдЯреЛрдХреНрдпреВрд░реЗрдВрд╕реА рдкреЛрд░реНрдЯрдлреЛрд▓рд┐рдпреЛ рдХреЗ рдореЙрдбрд▓, рдПрдХ рдЬреЛрдбрд╝реА рдореБрджреНрд░рд╛ рд╡рд┐рдирд┐рдордп рджрд░, рдХреНрд░рд┐рдкреНрдЯреЛрдХреНрдпреВрд░реЗрдВрд╕реА рдХреА рдХреАрдорддреЛрдВ рдореЗрдВ рдлрд╝рд┐рдПрдЯ рдореБрджреНрд░рд╛ рдореЗрдВ, рдФрд░ рдкреЛрд░реНрдЯрдлреЛрд▓рд┐рдпреЛ рдХрд╛ рдХреБрд▓ рдореВрд▓реНрдпред


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


 Portfolio.java package com.room606.cryptonaut.domain; import java.math.BigDecimal; import java.util.Collections; import java.util.Map; import java.util.TreeMap; public class Portfolio { private Map<String, BigDecimal> coins = Collections.emptyMap(); public Map<String, BigDecimal> getCoins() { return new TreeMap<>(coins); } public void setCoins(Map<String, BigDecimal> coins) { this.coins = coins; } 

 FiatRate.java package com.room606.cryptonaut.domain; import java.math.BigDecimal; public class FiatRate { private String base; private String counter; private BigDecimal value; public FiatRate(String base, String counter, BigDecimal value) { this.base = base; this.counter = counter; this.value = value; } public String getBase() { return base; } public void setBase(String base) { this.base = base; } public String getCounter() { return counter; } public void setCounter(String counter) { this.counter = counter; } public BigDecimal getValue() { return value; } public void setValue(BigDecimal value) { this.value = value; } } 

 Price.java ... Prices.java () ... Total.java ... 

рдирд┐рдпрдВрддреНрд░рдХреЛрдВ


рд╣рдо рдПрдХ рдирд┐рдпрдВрддреНрд░рдХ рд▓рд┐рдЦрдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░ рд░рд╣реЗ рд╣реИрдВ рдЬреЛ рд╕рд░рд▓рддрдо рдПрдкреАрдЖрдИ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рддрд╛ рд╣реИ, рд╕рд┐рдХреНрдХреЛрдВ рдХреЗ рджрд┐рдП рдЧрдП рдкрддреНрд░ рдХреЛрдб рдХреЗ рдЕрдиреБрд╕рд╛рд░ рдХреНрд░рд┐рдкреНрдЯреЛрдХрд░реЗрдВрд╕реА рдХрд╛ рдореВрд▓реНрдп рдЬрд╛рд░реА рдХрд░рддрд╛ рд╣реИред
рдпрд╛рдиреА


 GET /cryptonaut/restapi/prices.json?coins=BTC&coins=ETH&fiatCurrency=RUR 

рдХреБрдЫ рдХрд╛ рдЙрддреНрдкрд╛рджрди рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдП:


 {"prices":[{"coin":"BTC","value":407924.043300000000},{"coin":"ETH","value":13040.638266000000}],"fiatCurrency":"RUR"} 

рдкреНрд░рд▓реЗрдЦрди рдХреЗ рдЕрдиреБрд╕рд╛рд░, рдХреБрдЫ рднреА рдЬрдЯрд┐рд▓ рдирд╣реАрдВ рд╣реИ рдФрд░ рдПрдХ рд╣реА Spring рджреГрд╖реНрдЯрд┐рдХреЛрдг рдФрд░ рд╕рдореНрдореЗрд▓рдиреЛрдВ рдХреА рдпрд╛рдж рджрд┐рд▓рд╛рддрд╛ рд╣реИ:


 package com.room606.cryptonaut.rest; import com.room606.cryptonaut.domain.Price; import com.room606.cryptonaut.domain.Prices; import com.room606.cryptonaut.markets.FiatExchangeRatesService; import com.room606.cryptonaut.markets.CryptoMarketDataService; import io.micronaut.http.MediaType; import io.micronaut.http.annotation.*; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @Controller("/cryptonaut/restapi/") public class MarketDataController { private final CryptoMarketDataService cryptoMarketDataService; private final FiatExchangeRatesService fiatExchangeRatesService; public MarketDataController(CryptoMarketDataService cryptoMarketDataService, FiatExchangeRatesService fiatExchangeRatesService) { this.cryptoMarketDataService = cryptoMarketDataService; this.fiatExchangeRatesService = fiatExchangeRatesService; } @Get("/prices.json") @Produces(MediaType.APPLICATION_JSON) public Prices pricesAsJson(@QueryValue("coins") String[] coins, @QueryValue("fiatCurrency") String fiatCurrency) { return getPrices(coins, fiatCurrency); } private Prices getPrices(String[] coins, String fiatCurrency) { List<Price> prices = Stream.of(coins) .map(coin -> new Price(coin, cryptoMarketDataService.getPrice(coin, fiatCurrency))) .collect(Collectors.toList()); return new Prices(prices, fiatCurrency); } } 

рдпрд╛рдиреА рд╣рдо рд╢рд╛рдВрддрд┐ рд╕реЗ рдЕрдкрдиреЗ POJO рдХреЛ рд▓реМрдЯрд╛рдП рдЧрдП рдкреНрд░рдХрд╛рд░ рдХреЗ рд░реВрдк рдореЗрдВ рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдХрд░рддреЗ рд╣реИрдВ, рдФрд░ рдмрд┐рдирд╛ рдХрд┐рд╕реА рдХреНрд░рдорд┐рдХ / deserializers рдХреЛ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд┐рдП рдмрд┐рдирд╛, рдпрд╣рд╛рдВ рддрдХ тАЛтАЛрдХрд┐ рдЕрддрд┐рд░рд┐рдХреНрдд рдПрдиреЛрдЯреЗрд╢рди рдХреЛ рд▓рдЯрдХрд╛рдП рдмрд┐рдирд╛ рднреА рдмреЙрдХреНрд╕ рд╕реЗ рдбреЗрдЯрд╛ рдХреЗ рд╕рд╛рде рд╕рд╣реА http рдмреЙрдбреА рдмрдирд╛рдПрдВрдЧреЗред рдЪрд▓реЛ Spring рд░рд╛рд╕реНрддреЗ рд╕реЗ рддреБрд▓рдирд╛ рдХрд░реЗрдВ:


 @RequestMapping(value = "/prices.json", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity<Prices> pricesAsJson(@RequestParam("userId") final String[] coins, @RequestParam("fiatCurrency") String fiatCurrency) { 

рдкреНрд░рд▓реЗрдЦрди рдХреЗ рдЕрдиреБрд╕рд╛рд░, рд╕рд╛рдорд╛рдиреНрдп рддреМрд░ рдкрд░, рдореБрдЭреЗ рдирд┐рдпрдВрддреНрд░рдХреЛрдВ рдХреЗ рд╕рд╛рде рдХреЛрдИ рд╕рдорд╕реНрдпрд╛ рдирд╣реАрдВ рдереА, рдЙрдиреНрд╣реЛрдВрдиреЗ рд╕рд┐рд░реНрдл рдЙрдирд╕реЗ рдЕрдкреЗрдХреНрд╖рд╛ рдХреЗ рдЕрдиреБрд░реВрдк рдХрд╛рдо рдХрд┐рдпрд╛ред рдЙрдирдХреА рд╡рд░реНрддрдиреА рд╕рд╣рдЬ рдФрд░ рд╕рд░рд▓ рдереАред рд╣рдо рдЖрдЧреЗ рдмрдврд╝рддреЗ рд╣реИрдВред


рдбреЗрдЯрд╛ рднрдВрдбрд╛рд░рдг рдкрд░рдд


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


рдбреЗрдЯрд╛ рджреГрдврд╝рддрд╛ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдкреНрд░рд▓реЗрдЦрди рдЬреЗрдкреАрдП рдХрдиреЗрдХреНрд╢рди рдХреЗ рд╕рд╛рде-рд╕рд╛рде рдбреЗрдЯрд╛рдмреЗрд╕ рд╕реЗ рдкрдврд╝рдиреЗ рдХреЗ рд▓рд┐рдП рд╡рд┐рднрд┐рдиреНрди рдЧреНрд░рд╛рд╣рдХреЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреЗ рдЦрдВрдбрд┐рдд рдЙрджрд╛рд╣рд░рдг (рдЕрдиреБрднрд╛рдЧ "12.1.5 рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдкреЛрд╕реНрдЯрдЧреНрд░реЗрдЬ") рдХреЗ рд╡рд┐рдХрд▓реНрдк рдкреНрд░рджрд╛рди рдХрд░рддрд╛ рд╣реИред JPA рдирд┐рд░реНрдгрд╛рдпрдХ рд░реВрдк рд╕реЗ рдЦрд╛рд░рд┐рдЬ рдХрд░ рджрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ рдФрд░ рдкреНрд░рд╢реНрдиреЛрдВ рдХреЛ рд▓рд┐рдЦрдиреЗ рдФрд░ рд╣реЗрд░рдлреЗрд░ рдХрд░рдиреЗ рдХреЛ рдкреНрд░рд╛рдердорд┐рдХрддрд╛ рджреА рдЧрдИ рдереАред рджрд╕реНрддрд╛рд╡реЗрдЬрд╝реАрдХрд░рдг рдХреЗ рдЕрдиреБрд╕рд╛рд░ рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рдХреЛ application.yml рдореЗрдВ рдЬреЛрдбрд╝рд╛ рдЧрдпрд╛ рдерд╛, ( Postgres рдХреЛ RDBMS рдХреЗ рд░реВрдк рдореЗрдВ рдЪреБрдирд╛ рдЧрдпрд╛ рдерд╛):


 postgres: reactive: client: port: 5432 host: localhost database: cryptonaut user: crypto password: r1ch13r1ch maxSize: 5 

postgres-reactive рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХреЗ рдЖрдзрд╛рд░ рдкрд░ рдЬреЛрдбрд╝рд╛ рдЧрдпрд╛ рдерд╛ред рдпрд╣ рдПрдХ рдЕрддреБрд▓реНрдпрдХрд╛рд▓рд┐рдХ рддрд░реАрдХреЗ рд╕реЗ рдФрд░ рдПрдХ рддреБрд▓реНрдпрдХрд╛рд▓рд┐рдХ рдореЗрдВ рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдЧреНрд░рд╛рд╣рдХ рд╣реИред


 <dependency> <groupId>io.micronaut.configuration</groupId> <artifactId>postgres-reactive</artifactId> <version>1.0.0.M4</version> <scope>compile</scope> </dependency> 

рдФрд░ рдЕрдВрдд рдореЗрдВ, рд╣рдорд╛рд░реЗ рдЖрд╡реЗрджрди рдХреЗ рднрд╡рд┐рд╖реНрдп рдХреЗ рд╡рд╛рддрд╛рд╡рд░рдг рдХреЛ рддреИрдирд╛рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП docker-compose.yml рдлрд╝рд╛рдЗрд▓ docker-compose.yml рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рдЬреЛрдбрд╝рд╛ рдЧрдпрд╛ рдерд╛, рдЬрд╣рд╛рдВ рдбреЗрдЯрд╛рдмреЗрд╕ рдШрдЯрдХ рдЬреЛрдбрд╝рд╛ рдЧрдпрд╛ рдерд╛:


 db: image: postgres:9.6 restart: always environment: POSTGRES_USER: crypto POSTGRES_PASSWORD: r1ch13r1ch POSTGRES_DB: cryptonaut ports: - 5432:5432 volumes: - ${PWD}/../db/init_tables.sql:/docker-entrypoint-initdb.d/1.0.0_init_tables.sql 

рдиреАрдЪреЗ рдПрдХ рдмрд╣реБрдд рд╣реА рд╕рд░рд▓ рдЯреЗрдмрд▓ рд╕рдВрд░рдЪрдирд╛ рдХреЗ рд╕рд╛рде рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЗ рд▓рд┐рдП рдЖрд░рдВрднреАрдХрд░рдг рд╕реНрдХреНрд░рд┐рдкреНрдЯ рд╣реИ:


 CREATE TABLE portfolio ( id serial CONSTRAINT coin_amt_primary_key PRIMARY KEY, coin varchar(16) NOT NULL UNIQUE, amount NUMERIC NOT NULL ); 

рдЕрдм рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЗ рдкреЛрд░реНрдЯрдлреЛрд▓рд┐рдпреЛ рдХреЛ рдЕрдкрдбреЗрдЯ рдХрд░рдиреЗ рд╡рд╛рд▓реЗ рдХреЛрдб рдХреЛ рдлреЗрдВрдХрдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░рддреЗ рд╣реИрдВред рд╣рдорд╛рд░реЗ рдкреЛрд░реНрдЯрдлреЛрд▓рд┐рдпреЛ рдШрдЯрдХ рдЗрд╕ рддрд░рд╣ рджрд┐рдЦреЗрдЧрд╛:


 package com.room606.cryptonaut; import com.room606.cryptonaut.domain.Portfolio; import java.math.BigDecimal; import java.util.Optional; public interface PortfolioService { Portfolio savePortfolio(Portfolio portfolio); Portfolio loadPortfolio(); Optional<BigDecimal> calculateTotalValue(Portfolio portfolio, String fiatCurrency); } 

Postgres reactive client рд╡рд┐рдзрд┐рдпреЛрдВ рдХреЗ рд╕реЗрдЯ рдХреЛ рджреЗрдЦрддреЗ рд╣реБрдП, рд╣рдо рдЗрд╕ рд╡рд░реНрдЧ рдХреЛ рдлреЗрдВрдХ рд░рд╣реЗ рд╣реИрдВ:


 package com.room606.cryptonaut; import com.room606.cryptonaut.domain.Portfolio; import com.room606.cryptonaut.markets.CryptoMarketDataService; import io.micronaut.context.annotation.Requires; import io.reactiverse.pgclient.Numeric; import io.reactiverse.reactivex.pgclient.*; import javax.inject.Inject; import java.math.BigDecimal; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; public class PortfolioServiceImpl implements PortfolioService { private final PgPool pgPool; ... private static final String UPDATE_COIN_AMT = "INSERT INTO portfolio (coin, amount) VALUES (?, ?) ON CONFLICT (coin) " + "DO UPDATE SET amount = ?"; ... public Portfolio savePortfolio(Portfolio portfolio) { List<Tuple> records = portfolio.getCoins() .entrySet() .stream() .map(entry -> Tuple.of(entry.getKey(), Numeric.create(entry.getValue()), Numeric.create(entry.getValue()))) .collect(Collectors.toList()); pgPool.preparedBatch(UPDATE_COIN_AMT, records, pgRowSetAsyncResult -> { //   pgRowSetAsyncResult.cause().printStackTrace(); }); return portfolio; } ... } 

рдПрдХ рдкрд░реНрдпрд╛рд╡рд░рдг рд╢реБрд░реВ рдХрд░рддрд╛ рд╣реИ, рд╣рдо рдЕрдкрдиреЗ рдкреЛрд░реНрдЯрдлреЛрд▓рд┐рдпреЛ рдХреЛ рдкрд╣рд▓реЗ рд╕реЗ рд╕рдордЭрджрд╛рд░реА рд╕реЗ рд▓рд╛рдЧреВ рдПрдкреАрдЖрдИ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдЕрдкрдбреЗрдЯ рдХрд░рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░рддреЗ рд╣реИрдВ:


 package com.room606.cryptonaut.rest; import com.room606.cryptonaut.PortfolioService; import com.room606.cryptonaut.domain.Portfolio; import io.micronaut.http.MediaType; import io.micronaut.http.annotation.*; import javax.inject.Inject; @Controller("/cryptonaut/restapi/") public class ConfigController { @Inject private PortfolioService portfolioService; @Post("/portfolio") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Portfolio savePortfolio(@Body Portfolio portfolio) { return portfolioService.savePortfolio(portfolio); } 

curl рдЕрдиреБрд░реЛрдз рдХрд░реЗрдВ:


 curl http://localhost:8080/cryptonaut/restapi/portfolio -X POST -H "Content-Type: application/json" --data '{"coins": {"XRP": "35.5", "LSK": "5.03", "XEM": "16.23"}}' -v 

рдФрд░ ... рд▓реЙрдЧ рдореЗрдВ рддреНрд░реБрдЯрд┐ рдкрдХрдбрд╝:


 io.reactiverse.pgclient.PgException: syntax error at or near "," at io.reactiverse.pgclient.impl.PrepareStatementCommand.handleErrorResponse(PrepareStatementCommand.java:74) at io.reactiverse.pgclient.impl.codec.decoder.MessageDecoder.decodeError(MessageDecoder.java:250) at io.reactiverse.pgclient.impl.codec.decoder.MessageDecoder.decodeMessage(MessageDecoder.java:139) ... 

рд╣рдорд╛рд░реЗ рд╢рд▓рдЬрдо рдХреЛ рдЦрдВрдЧрд╛рд▓рдиреЗ рдХреЗ рдмрд╛рдж, рд╣рдореЗрдВ рдЖрдзрд┐рдХрд╛рд░рд┐рдХ рдЧреЛрджреА рдореЗрдВ рдХреЛрдИ рд╕рдорд╛рдзрд╛рди рдирд╣реАрдВ рдорд┐рд▓рддрд╛ рд╣реИ, рд╣рдо postgres-reactive рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдкрд░ рдбреЙрдХ рдХреЛ рдЧреВрдЧрд▓ рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░рддреЗ рд╣реИрдВ, рдФрд░ рдпрд╣ рд╕рд╣реА рд╕рдорд╛рдзрд╛рди рд╣реИ, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП рдФрд░ рд╕рд╣реА рдХреНрд╡реЗрд░реА рд╕рд┐рдВрдЯреИрдХреНрд╕ рдХреЛ рд╡рд┐рд╕реНрддрд╛рд░ рд╕реЗ рджрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред рдпрд╣ рдкреНрд▓реЗрд╕рд╣реЛрд▓реНрдбрд░-рдорд╛рдкрджрдВрдбреЛрдВ рдХрд╛ рдорд╛рдорд▓рд╛ рдерд╛, рдпрд╣ рдкрддрд╛ рдЪрд▓рд╛ рд╣реИ, рдЖрдкрдХреЛ рдлреЙрд░реНрдо рдХреЗ рдЧрд┐рдиреЗ рд╣реБрдП рд▓реЗрдмрд▓ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ $x ($1, $2, etc.) ред рддреЛ, рд▓рдХреНрд╖реНрдп рдЕрдиреБрд░реЛрдз рдХреЛ рдлрд┐рд░ рд╕реЗ рд▓рд┐рдЦрдирд╛ рд╣реИ:


 private static final String UPDATE_COIN_AMT = "INSERT INTO portfolio (coin, amount) VALUES ($1, $2) ON CONFLICT (coin) " + "DO UPDATE SET amount = $3"; 

рд╣рдо рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЛ рдкреБрдирдГ рдЖрд░рдВрдн рдХрд░рддреЗ рд╣реИрдВ, рдЙрд╕реА REST рдЕрдиреБрд░реЛрдз ... рдЪреАрдпрд░реНрд╕ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░реЗрдВред рдбреЗрдЯрд╛ рдХреЛ рдЬреЛрдбрд╝рд╛ рдЬрд╛рддрд╛ рд╣реИред рдЪрд▓рд┐рдП рдкрдврд╝рддреЗ рд╣реИрдВред


рд╣рдореЗрдВ рдбреЗрдЯрд╛рдмреЗрд╕ рд╕реЗ рдХрд┐рд╕реА рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреА рдХреНрд░рд┐рдкреНрдЯреЛрдХрд░реЗрдВрд╕реА рдХреЗ рдкреЛрд░реНрдЯрдлреЛрд▓рд┐рдпреЛ рдХреЛ рдкрдврд╝рдиреЗ рдФрд░ рдкреАрдУрдЬреЗрдУ рдСрдмреНрдЬреЗрдХреНрдЯ рдкрд░ рдореИрдк рдХрд░рдиреЗ рдХреЗ рд╕рдмрд╕реЗ рд╕рд░рд▓ рдХрд╛рд░реНрдп рдХреЗ рд╕рд╛рде рд╕рд╛рдордирд╛ рдХрд░рдирд╛ рдкрдбрд╝рддрд╛ рд╣реИред рдЗрди рдЙрджреНрджреЗрд╢реНрдпреЛрдВ рдХреЗ рд▓рд┐рдП, рд╣рдо pgPool.query рд╡рд┐рдзрд┐ (SELECT_COINS_AMTS, pgRowSetAsyncResult) рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ:


 public Portfolio loadPortfolio() { Map<String, BigDecimal> coins = new HashMap<>(); pgPool.query(SELECT_COINS_AMTS, pgRowSetAsyncResult -> { if (pgRowSetAsyncResult.succeeded()) { PgRowSet rows = pgRowSetAsyncResult.result(); PgIterator pgIterator = rows.iterator(); while (pgIterator.hasNext()) { Row row = pgIterator.next(); coins.put(row.getString("coin"), new BigDecimal(row.getFloat("amount"))); } } else { System.out.println("Failure: " + pgRowSetAsyncResult.cause().getMessage()); } }); Portfolio portfolio = new Portfolio(); portfolio.setCoins(coins); return portfolio; } 

рд╣рдо рдХреНрд░рд┐рдкреНрдЯреЛрдХреНрдпреВрд░реЗрдВрд╕реА рдкреЛрд░реНрдЯрдлреЛрд▓рд┐рдпреЛ рдХреЗ рд▓рд┐рдП рдЬрд┐рдореНрдореЗрджрд╛рд░ рдирд┐рдпрдВрддреНрд░рдХ рдХреЗ рд╕рд╛рде рдпрд╣ рд╕рдм рдПрдХ рд╕рд╛рде рдЬреЛрдбрд╝рддреЗ рд╣реИрдВ:


 @Controller("/cryptonaut/restapi/") public class ConfigController { ... @Get("/portfolio") @Produces(MediaType.APPLICATION_JSON) public Portfolio loadPortfolio() { return portfolioService.loadPortfolio(); } ... 

рд╕реЗрд╡рд╛ рдХреЛ рдкреБрдирд░рд╛рд░рдВрдн рдХрд░реЗрдВред рдкрд░реАрдХреНрд╖рдг рдХреЗ рд▓рд┐рдП, рд╣рдо рдкрд╣рд▓реЗ рдЗрд╕ рдкреЛрд░реНрдЯрдлреЛрд▓рд┐рдпреЛ рдХреЛ рдХрдо рд╕реЗ рдХрдо рдХреБрдЫ рдбреЗрдЯрд╛ рдХреЗ рд╕рд╛рде рднрд░рддреЗ рд╣реИрдВ:


 curl http://localhost:8080/cryptonaut/restapi/portfolio -X POST -H "Content-Type: application/json" --data '{"coins": {"XRP": "35.5", "LSK": "5.03", "XEM": "16.23"}}' -v 

рдЕрдм рдЕрдВрдд рдореЗрдВ рдбреЗрдЯрд╛рдмреЗрд╕ рд╕реЗ рд╣рдорд╛рд░реЗ рдХреЛрдб рд░реАрдбрд┐рдВрдЧ рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░реЗрдВ:


 curl http://localhost:8080/cryptonaut/restapi/portfolio -v 

рдФрд░ ... рд╣рдореЗрдВ рдорд┐рд▓рддрд╛ рд╣реИ ... рдХреБрдЫ рдЕрдЬреАрдм:


 {"coins":{}} 

рдмрд╣реБрдд рдЕрдЬреАрдм рд╣реИ рдирд╛? рд╣рдо рдЕрдиреБрд░реЛрдз рдХреЛ рджрд╕ рдмрд╛рд░ curl рд╣реИрдВ, curl рдЕрдиреБрд░реЛрдз рдХреЛ рдлрд┐рд░ рд╕реЗ рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░реЗрдВ, рдпрд╣рд╛рдВ рддрдХ тАЛтАЛрдХрд┐ рд╣рдорд╛рд░реА рд╕реЗрд╡рд╛ рдХреЛ рдлрд┐рд░ рд╕реЗ рд╢реБрд░реВ рдХрд░реЗрдВред рдкрд░рд┐рдгрд╛рдо рдЕрднреА рднреА рд╡рд╣реА рдЬрдВрдЧрд▓реА рд╣реИ ... рд╡рд┐рдзрд┐ рдХреЗ рд╣рд╕реНрддрд╛рдХреНрд╖рд░ рдХреЛ рдлрд┐рд░ рд╕реЗ рдкрдврд╝рдиреЗ рдХреЗ рдмрд╛рдж, рдФрд░ рдпрд╣ рднреА рдпрд╛рдж рд░рдЦрдирд╛ рдХрд┐ рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдПрдХ Reactive Pg client , рд╣рдо рдЗрд╕ рдирд┐рд╖реНрдХрд░реНрд╖ рдкрд░ рдЖрддреЗ рд╣реИрдВ рдХрд┐ рд╣рдо рдПрд╕рд┐рдВрдХреНрд░рдирд╛рдЗрдЬрд╝реЗрд╢рди рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░ рд░рд╣реЗ рд╣реИрдВред рд╡рд┐рдЪрд╛рд░рд╢реАрд▓ рдмрд╣рд╕ рдиреЗ рдЗрд╕рдХреА рдкреБрд╖реНрдЯрд┐ рдХреА! рдпрд╣ рдереЛрдбрд╝рд╛ рдЗрддреНрдореАрдирд╛рди рд╕реЗ рдбреЗ-рдХреЛрдб рдХреЗ рд▓рд╛рдпрдХ рдерд╛, рдЬреИрд╕реЗ рдХрд┐ рд╡реЙрдЗрд▓рд╛, рд╣рдореЗрдВ рдЧреИрд░-рдЦрд╛рд▓реА рдбреЗрдЯрд╛ рдорд┐рд▓рд╛!


рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдбреЙрдХ рдХреА рдУрд░ рдлрд┐рд░ рд╕реЗ рдореБрдбрд╝рддреЗ рд╣реБрдП, рд╣рдорд╛рд░реА рдЖрд╕реНрддреАрди рдХреЛ рдКрдкрд░ рдЙрдард╛рддреЗ рд╣реБрдП, рд╣рдо рдПрдХ рд╕рдЪреНрдЪреЗ рдЕрд╡рд░реЛрдзрдХ рдХреЛрдб рдХреЗ рд╕рд╛рде рдХреЛрдб рдХреЛ рдлрд┐рд░ рд╕реЗ рд▓рд┐рдЦрддреЗ рд╣реИрдВ, рд▓реЗрдХрд┐рди рдкреВрд░реА рддрд░рд╣ рд╕реЗ рднрд╡рд┐рд╖реНрдпрд╡рд╛рдгреА рдХрд░рддреЗ рд╣реИрдВ:


 Map<String, BigDecimal> coins = new HashMap<>(); PgIterator pgIterator = pgPool.rxPreparedQuery(SELECT_COINS_AMTS).blockingGet().iterator(); while (pgIterator.hasNext()) { Row row = pgIterator.next(); coins.put(row.getString("coin"), new BigDecimal(row.getValue("amount").toString())); } 

рдЕрдм рд╣рдореЗрдВ рд╡рд╣ рдорд┐рд▓рддрд╛ рд╣реИ рдЬрд┐рд╕рдХреА рд╣рдо рдЕрдкреЗрдХреНрд╖рд╛ рдХрд░рддреЗ рд╣реИрдВред рд╣рдордиреЗ рдЗрд╕ рд╕рдорд╕реНрдпрд╛ рдХрд╛ рдлреИрд╕рд▓рд╛ рдХрд┐рдпрд╛, рдЖрдЧреЗ рдмрдврд╝реЗрдВред


рд╣рдо рдмрд╛рдЬрд╛рд░ рдбреЗрдЯрд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХреНрд▓рд╛рдЗрдВрдЯ рд▓рд┐рдЦрддреЗ рд╣реИрдВ


рдпрд╣рд╛рдВ, рдирд┐рд╢реНрдЪрд┐рдд рд░реВрдк рд╕реЗ, рдореИрдВ рдХрдо рд╕реЗ рдХрдо рдмрд╛рдЗрдХ рдХреЗ рд╕рд╛рде рд╕рдорд╕реНрдпрд╛ рдХреЛ рд╣рд▓ рдХрд░рдирд╛ рдЪрд╛рд╣реВрдВрдЧрд╛ред рдкрд░рд┐рдгрд╛рдо рджреЛ рд╕рдорд╛рдзрд╛рди рд╣реИрдВ:


  • рд╡рд┐рд╢рд┐рд╖реНрдЯ рдХреНрд░рд┐рдкреНрдЯреЛ рдПрдХреНрд╕рдЪреЗрдВрдЬреЛрдВ рддрдХ рдкрд╣реБрдВрдЪрдиреЗ рдХреЗ рд▓рд┐рдП рддреИрдпрд╛рд░ рдЧреНрд░рд╛рд╣рдХ рдкреБрд╕реНрддрдХрд╛рд▓рдп
  • рд╡рд┐рдирд┐рдордп рджрд░ рдХреЗ рд▓рд┐рдП рдЖрд╡реЗрджрди рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдЫреЛрдЯреЗ рдЧреНрд░рд╛рд╣рдХ рдХрд╛ рдЕрдкрдирд╛ рдХреЛрдбред рдХреНрдпрд╛ рдмреЙрдХреНрд╕ рд╕реЗ рдмрд╛рд╣рд░ рдЖ рдЧрдпрд╛ рд╣реИред

рддреИрдпрд╛рд░ рдкреБрд╕реНрддрдХрд╛рд▓рдпреЛрдВ рдХреЗ рд╕рд╛рде, рд╕рдм рдХреБрдЫ рдЗрддрдирд╛ рджрд┐рд▓рдЪрд╕реНрдк рдирд╣реАрдВ рд╣реИред рдореИрдВ рдХреЗрд╡рд▓ рдзреНрдпрд╛рди рджреЗрддрд╛ рд╣реВрдВ рдХрд┐ рдПрдХ рддреНрд╡рд░рд┐рдд рдЦреЛрдЬ рдХреЗ рджреМрд░рд╛рди, рдкрд░рд┐рдпреЛрдЬрдирд╛ https://github.com/knowm/XChange рдХрд╛ рдЪрдпрди рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ред


рд╕рд┐рджреНрдзрд╛рдВрдд рд░реВрдк рдореЗрдВ, рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдЖрд░реНрдХрд┐рдЯреЗрдХреНрдЪрд░ рддреАрди рдкреЗрдиреА рдХреЗ рд░реВрдк рдореЗрдВ рд╕рд░рд▓ рд╣реИ - рдбреЗрдЯрд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЗрдВрдЯрд░рдлреЗрд╕ рдХрд╛ рдПрдХ рд╕реЗрдЯ рд╣реИ, Ticker рдЬреИрд╕реЗ рдореБрдЦреНрдп рдЗрдВрдЯрд░рдлреЗрд╕ рдФрд░ рдореЙрдбрд▓ рдХрдХреНрд╖рд╛рдПрдВ (рдЖрдк bid рд╕рдХрддреЗ рд╣реИрдВ, ask рд╕рдХрддреЗ рд╣реИрдВ, рд╕рднреА рдкреНрд░рдХрд╛рд░ рдХреА рдЦреБрд▓реА рдХреАрдордд, рдХрд░реАрдмреА рдореВрд▓реНрдп рдЖрджрд┐), CurrencyPair , Currency ред рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдЖрдк рдХреЛрдб рдореЗрдВ рдЦреБрдж рдХреЛ рдЗрдирд┐рд╢рд┐рдпрд▓рд╛рдЗрдЬрд╝реЗрд╢рди рдЗрдирд┐рд╢рд┐рдпрд▓рд╛рдЗрдЬрд╝ рдХрд░рддреЗ рд╣реИрдВ, рдкрд╣рд▓реЗ рд╕реЗ рдбрд┐рдкреЗрдВрдбреЗрдВрд╕реА рдХреЛ рдЗрдореНрдкреНрд▓реАрдореЗрдВрдЯреЗрд╢рди рд╕реЗ рдЬреЛрдбрд╝рддреЗ рд╣реИрдВ рдЬреЛ рдПрдХ рд╡рд┐рд╢рд┐рд╖реНрдЯ рдХреНрд░рд┐рдкреНрдЯреЛрдХрд░рдВрд╕реА рдХреЛ рд╕рдВрджрд░реНрднрд┐рдд рдХрд░рддрд╛ рд╣реИред рдФрд░ рд╣рдорд╛рд░реЗ рджреНрд╡рд╛рд░рд╛ рд╕рдВрдЪрд╛рд▓рд┐рдд рдореБрдЦреНрдп рд╡рд░реНрдЧ MarketDataService.java


рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рд╣рдорд╛рд░реЗ рдкреНрд░рдпреЛрдЧреЛрдВ рдХреЗ рд▓рд┐рдП, рд╢реБрд░реБрдЖрдд рдХреЗ рд▓рд┐рдП, рд╣рдо рдРрд╕реЗ "рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди" рд╕реЗ рд╕рдВрддреБрд╖реНрдЯ рд╣реИрдВ:


 <dependency> <groupId>org.knowm.xchange</groupId> <artifactId>xchange-core</artifactId> <version>4.3.10</version> </dependency> <dependency> <groupId>org.knowm.xchange</groupId> <artifactId>xchange-bittrex</artifactId> <version>4.3.10</version> </dependency> 

рдиреАрдЪреЗ рдПрдХ рдорд╣рддреНрд╡рдкреВрд░реНрдг рдХрд╛рд░реНрдп рдХрд░рдиреЗ рд╡рд╛рд▓рд╛ рдХреЛрдб рд╣реИ - рд╡рд┐рд╢реЗрд╖ рд╢рдмреНрджреЛрдВ рдореЗрдВ рдХрд┐рд╕реА рд╡рд┐рд╢реЗрд╖ рдХреНрд░рд┐рдкреНрдЯреЛрдХрд░реЗрдВрд╕реА рдХреА рд▓рд╛рдЧрдд рдХреА рдЧрдгрдирд╛ рдХрд░рдирд╛ (рдЖрд╡рд╢реНрдпрдХрддрд╛рдУрдВ рдмреНрд▓реЙрдХ рдореЗрдВ рд▓реЗрдЦ рдХреА рд╢реБрд░реБрдЖрдд рдореЗрдВ рд╡рд░реНрдгрд┐рдд рд╕реВрддреНрд░ рджреЗрдЦреЗрдВ):


 package com.room606.cryptonaut.markets; import com.room606.cryptonaut.exceptions.CryptonautException; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.Ticker; import org.knowm.xchange.exceptions.CurrencyPairNotValidException; import org.knowm.xchange.service.marketdata.MarketDataService; import javax.inject.Inject; import javax.inject.Singleton; import java.io.IOException; import java.math.BigDecimal; @Singleton public class CryptoMarketDataService { private final FiatExchangeRatesService fiatExchangeRatesService; private final MarketDataService marketDataService; @Inject public CryptoMarketDataService(FiatExchangeRatesService fiatExchangeRatesService, MarketDataServiceFactory marketDataServiceFactory) { this.fiatExchangeRatesService = fiatExchangeRatesService; this.marketDataService = marketDataServiceFactory.getMarketDataService(); } public BigDecimal getPrice(String coinCode, String fiatCurrencyCode) throws CryptonautException { BigDecimal price = getPriceForBasicCurrency(coinCode, Currency.USD.getCurrencyCode()); if (Currency.USD.equals(new Currency(fiatCurrencyCode))) { return price; } else { return price.multiply(fiatExchangeRatesService.getFiatPrice(Currency.USD.getCurrencyCode(), fiatCurrencyCode)); } } private BigDecimal getPriceForBasicCurrency(String coinCode, String fiatCurrencyCode) throws CryptonautException { Ticker ticker = null; try { ticker = marketDataService.getTicker(new CurrencyPair(new Currency(coinCode), new Currency(fiatCurrencyCode))); return ticker.getBid(); } catch (CurrencyPairNotValidException e) { ticker = getTicker(new Currency(coinCode), Currency.BTC); Ticker ticker2 = getTicker(Currency.BTC, new Currency(fiatCurrencyCode)); return ticker.getBid().multiply(ticker2.getBid()); } catch (IOException e) { throw new CryptonautException("Failed to get price for Pair " + coinCode + "/" + fiatCurrencyCode + ": " + e.getMessage(), e); } } private Ticker getTicker(Currency base, Currency counter) throws CryptonautException { try { return marketDataService.getTicker(new CurrencyPair(base, counter)); } catch (CurrencyPairNotValidException | IOException e) { throw new CryptonautException("Failed to get price for Pair " + base.getCurrencyCode() + "/" + counter.getCurrencyCode() + ": " + e.getMessage(), e); } } } 

рдкреНрд░реЛрдЬреЗрдХреНрдЯ https://github.com/knowm/XChange рджреНрд╡рд╛рд░рд╛ рдкреНрд░рджрд╛рди рдХрд┐рдП рдЧрдП рд╡рд┐рд╢рд┐рд╖реНрдЯ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрдиреЛрдВ рдХреЛ рдереЛрдбрд╝рд╛ рдЕрдирджреЗрдЦрд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╣рдорд╛рд░реЗ рд╕реНрд╡рдпрдВ рдХреЗ рдЗрдВрдЯрд░рдлреЗрд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рд╕рдВрднрд╡ рд╣реЛ рдЧрдпрд╛ рд╣реИред


рдЗрд╕ рддрдереНрдп рдХреЗ рдорджреНрджреЗрдирдЬрд░ рдХрд┐ рдХрдИ рдкрд░, рдпрджрд┐ рд╕рднреА рдХреНрд░рд┐рдкреНрдЯреЛрдХреНрдпреВрд░реЗрдВрд╕реА рдПрдХреНрд╕рдЪреЗрдВрдЬ рдирд╣реАрдВ рдХрд░рддреЗ рд╣реИрдВ, рддреЛ рдкреНрд░рдЪрд▓рди рдореЗрдВ рдХреЗрд╡рд▓ fiat рдореБрджреНрд░рд╛рдУрдВ рдХрд╛ рдПрдХ рд╕реАрдорд┐рдд рд╕реЗрдЯ рд╣реИ (рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЗ рдкреНрд░рд╢реНрди рдХреЗ рдЕрдВрддрд┐рдо рдЙрддреНрддрд░ рдХреЗ рд▓рд┐рдП, USD (, EUR, рд╢рд╛рдпрдж рдпрд╣ рд╕рдм ..), рдПрдХ рдЕрдиреНрдп рдбреЗрдЯрд╛ рд╕реНрд░реЛрдд рдХреЛ рдЬреЛрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рд╣реИ - fiat рдореБрджреНрд░рд╛ рджрд░реЗрдВ, рдФрд░ рдПрдХ рд╡реИрдХрд▓реНрдкрд┐рдХ рдХрдирд╡рд░реНрдЯрд░ рднреАред рдпрд╛рдиреА рдЗрд╕ рд╕рд╡рд╛рд▓ рдХрд╛ рдЬрд╡рд╛рдм рджреЗрдиреЗ рдХреЗ рд▓рд┐рдП, рдЖрд░рдЯреАрдЖрд░ (рд▓рдХреНрд╖реНрдп рдореБрджреНрд░рд╛, рд▓рдХреНрд╖реНрдп рдореБрджреНрд░рд╛) рдореЗрдВ рдбрдмреНрд▓реНрдпреВрдЯреАрдПрдл рдХреНрд░рд┐рдкреНрдЯреЛрдХреНрдпреВрд░реЗрдВрд╕реА рдХреА рд▓рд╛рдЧрдд рдЕрдм рдХрд┐рддрдиреА рд╣реИ, рдЖрдкрдХреЛ рджреЛ рдЙрдк-рдкреНрд░рд╢реНрдиреЛрдВ рдХрд╛ рдЙрддреНрддрд░ рджреЗрдирд╛ рд╣реЛрдЧрд╛: рдбрдмреНрд▓реНрдпреВрдЯреАрдПрдл / рдмреЗрд╕рд╕реАрд░рд┐рдПрд╢рди (рд╣рдо рдпреВрдПрд╕рдбреА рдХреЛ рдЗрд╕ рддрд░рд╣ рдорд╛рдирддреЗ рд╣реИрдВ), рдмреЗрд╕рдХреЗрдВрдкреНрд░рд┐ / рдиреНрдпреВрд░рд▓, рдлрд┐рд░ рдЗрди рджреЛ рдореВрд▓реНрдпреЛрдВ рдХреЛ рдЧреБрдгрд╛ рдХрд░реЗрдВ рдФрд░ рдкрд░рд┐рдгрд╛рдорд╕реНрд╡рд░реВрдк рдЙрддреНрдкрдиреНрди рдХрд░реЗрдВред


рд╕реЗрд╡рд╛ рдХреЗ рд╣рдорд╛рд░реЗ рдкрд╣рд▓реЗ рд╕рдВрд╕реНрдХрд░рдг рдХреЗ рд▓рд┐рдП, рд╣рдо рдХреЗрд╡рд▓ рд▓рдХреНрд╖рд┐рдд рдореБрджреНрд░рд╛рдУрдВ рдХреЗ рд░реВрдк рдореЗрдВ рдпреВрдПрд╕рдбреА рдФрд░ рдЖрд░рдпреВрдЖрд░ рдХрд╛ рд╕рдорд░реНрдерди рдХрд░реЗрдВрдЧреЗред
рдЗрд╕рд▓рд┐рдП, рдЖрд░рдпреВрдЖрд░ рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдЙрди рд╕реНрд░реЛрддреЛрдВ рдХреЛ рд▓реЗрдирд╛ рдЙрдЪрд┐рдд рд╣реЛрдЧрд╛ рдЬреЛ рд╕реЗрд╡рд╛ рдХреА рднреМрдЧреЛрд▓рд┐рдХ рд╕реНрдерд┐рддрд┐ рдХреЗ рд▓рд┐рдП рдкреНрд░рд╛рд╕рдВрдЧрд┐рдХ рд╣реИрдВ (рд╣рдо рд░реВрд╕ рдореЗрдВ рдЗрд╕реЗ рд╡рд┐рд╢реЗрд╖ рд░реВрдк рд╕реЗ рд╣реЛрд╕реНрдЯ рдФрд░ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗ)ред рд╕рдВрдХреНрд╖реЗрдк рдореЗрдВ, рд╕реЗрдВрдЯреНрд░рд▓ рдмреИрдВрдХ рдХреА рджрд░ рд╣рдорд╛рд░реЗ рдЕрдиреБрд░реВрдк рд╣реЛрдЧреАред рдЗрд╕ рддрд░рд╣ рдХреЗ рдбреЗрдЯрд╛ рдХрд╛ рдПрдХ рдЦреБрд▓рд╛ рд╕реНрд░реЛрдд рдЗрдВрдЯрд░рдиреЗрдЯ рдкрд░ рдкрд╛рдпрд╛ рдЧрдпрд╛, рдЬрд┐рд╕реЗ JSON рдХреЗ рд░реВрдк рдореЗрдВ рд╕реЗрд╡рди рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред рдареАрдХред


рдиреАрдЪреЗ рд╡рд░реНрддрдорд╛рди рд╡рд┐рдирд┐рдордп рджрд░ рдЕрдиреБрд░реЛрдз рдХреЗ рд▓рд┐рдП рд╕реЗрд╡рд╛ рдХреА рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рд╣реИ:


 { "Date": "2018-10-16T11:30:00+03:00", "PreviousDate": "2018-10-13T11:30:00+03:00", "PreviousURL": "\/\/www.cbr-xml-daily.ru\/archive\/2018\/10\/13\/daily_json.js", "Timestamp": "2018-10-15T23:00:00+03:00", "Valute": { "AUD": { "ID": "R01010", "NumCode": "036", "CharCode": "AUD", "Nominal": 1, "Name": "╤Т╨Ж╨ГтАЪ╨В┬░┬╗тДЦ╨Г╤ФтДЦ ╥С╤Х┬╗┬╗┬░╨В", "Value": 46.8672, "Previous": 46.9677 }, "AZN": { "ID": "R01020A", "NumCode": "944", "CharCode": "AZN", "Nominal": 1, "Name": "╤Т┬╖┬╡╨В┬▒┬░тДЦ╥С┬╢┬░╨Е╨Г╤ФтДЦ ╤Ш┬░╨Е┬░тАЪ", "Value": 38.7567, "Previous": 38.8889 }, "GBP": { "ID": "R01035", "NumCode": "826", "CharCode": "GBP", "Nominal": 1, "Name": "┬д╤У╨ЕтАЪ ╨ГтАЪ┬╡╨В┬╗╨Е╤Ц╤Х╨Ж ╨О╤Х┬╡╥С╨Е┬╡╨Е╨Е╤Х╤Ц╤Х ╤Ф╤Х╨В╤Х┬╗┬╡╨Ж╨ГтАЪ╨Ж┬░", "Value": 86.2716, "Previous": 87.2059 }, ... 

рджрд░рдЕрд╕рд▓, рдиреАрдЪреЗ CbrExchangeRatesClient рдХреНрд▓рд╛рдЗрдВрдЯ CbrExchangeRatesClient :


 package com.room606.cryptonaut.markets.clients; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; import com.room606.cryptonaut.exceptions.CryptonautException; import io.micronaut.http.HttpRequest; import io.micronaut.http.client.Client; import io.micronaut.http.client.RxHttpClient; import javax.inject.Inject; import javax.inject.Singleton; import java.io.IOException; import java.math.BigDecimal; import java.util.*; @Singleton public class CbrExchangeRatesClient { private static final String CBR_DATA_URI = "https://www.cbr-xml-daily.ru/daily_json.js"; @Client(CBR_DATA_URI) @Inject private RxHttpClient httpClient; private final ObjectReader objectReader = new ObjectMapper().reader(); public Map<String, BigDecimal> getRates() { try { //return ratesCache.get("fiatRates"); HttpRequest<?> req = HttpRequest.GET(""); String response = httpClient.retrieve(req, String.class).blockingSingle(); JsonNode json = objectReader.readTree(response); String usdPrice = json.get("Valute").get("USD").get("Value").asText(); String eurPrice = json.get("Valute").get("EUR").get("Value").asText(); String gbpPrice = json.get("Valute").get("GBP").get("Value").asText(); Map<String, BigDecimal> prices = new HashMap<>(); prices.put("USD", new BigDecimal(usdPrice)); prices.put("GBP", new BigDecimal(gbpPrice)); prices.put("EUR", new BigDecimal(eurPrice)); return prices; } catch (IOException e) { throw new CryptonautException("Failed to obtain exchange rates: " + e.getMessage(), e); } } } 

рдпрд╣рд╛рдБ рд╣рдо RxHttpClient рдЗрдВрдЬреЗрдХреНрд╖рди RxHttpClient , рдЬреЛ рдХрд┐ RxHttpClient Micronaut рдПрдХ рдШрдЯрдХ рд╣реИред рдпрд╣ рд╣рдореЗрдВ рдЕрддреБрд▓реНрдпрдХрд╛рд▓рд┐рдХ рдЕрдиреБрд░реЛрдз рдкреНрд░рд╕рдВрд╕реНрдХрд░рдг рдпрд╛ рдЕрд╡рд░реБрджреНрдз рдХрд░рдиреЗ рдХрд╛ рд╡рд┐рдХрд▓реНрдк рднреА рджреЗрддрд╛ рд╣реИред рд╣рдо рдХреНрд▓рд╛рд╕рд┐рдХ рдмреНрд▓реЙрдХрд┐рдВрдЧ рдХрд╛ рдЪрдпрди рдХрд░рддреЗ рд╣реИрдВ:


 httpClient.retrieve(req, String.class).blockingSingle(); 

рд╡рд┐рдиреНрдпрд╛рд╕


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


рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдХреЛрдб рдореБрджреНрд░рд╛ рдХреЛрдб рдХреЛ рдЫреЛрдбрд╝ рджреЗрдЧрд╛ рдЬрд┐рд╕рдХреЗ рд▓рд┐рдП рд╣рдо рдЕрднреА рддрдХ рдкреЛрд░реНрдЯрдлреЛрд▓рд┐рдпреЛ рдореВрд▓реНрдп рдХреА рдЧрдгрдирд╛ рдХрд░рдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рдирд╣реАрдВ рд╣реИрдВ:


 public BigDecimal getFiatPrice(String baseCurrency, String counterCurrency) throws NotSupportedFiatException { if (!supportedCounterCurrencies.contains(counterCurrency)) { throw new NotSupportedFiatException("Counter currency not supported: " + counterCurrency); } Map<String, BigDecimal> rates = cbrExchangeRatesClient.getRates(); return rates.get(baseCurrency); } 

рддрджрдиреБрд╕рд╛рд░, рд╣рдорд╛рд░рд╛ рдЗрд░рд╛рджрд╛ рдХрд┐рд╕реА рднреА рддрд░рд╣ рд╕реЗ application.yml рд╕реЗ рдорд╛рди рдХреЛ рдЗрдВрдЬреЗрдХреНрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╣реИред


рдкрд╣рд▓реЗ рд╕рдВрд╕реНрдХрд░рдг рдореЗрдВ, рдЗрд╕ рддрд░рд╣ рдХреЗ рдХреЛрдб рдХреЛ FiatExchangeRatesService.java рд╡рд░реНрдЧ рдХреЗ рдХреНрд╖реЗрддреНрд░ рдХреЗ рдиреАрдЪреЗ рд▓рд┐рдЦрд╛ рдЧрдпрд╛ рдерд╛:


 @Value("${cryptonaut.currencies:RUR}") private String supportedCurrencies; private final List<String> supportedCounterCurrencies = Arrays.asList(supportedCurrencies.split("[,]", -1)); 

рдпрд╣рд╛рдБ, placeholder application.yml рдХреА рдирд┐рдореНрди рд╕рдВрд░рдЪрдирд╛ рд╕реЗ рдореЗрд▓ рдЦрд╛рддрд╛ рд╣реИред placeholder рджрд╕реНрддрд╛рд╡реЗрдЬ:


 micronaut: application: name: cryptonaut #Uncomment to set server port server: port: 8080 postgres: reactive: client: port: 5432 host: localhost database: cryptonaut user: crypto password: r1ch13r1ch maxSize: 5 # app / business logic specific properties cryptonaut: currencies: "RUR" 

рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд▓реЙрдиреНрдЪ, рддреНрд╡рд░рд┐рдд рдзреВрдореНрд░рдкрд╛рди рдкрд░реАрдХреНрд╖рдг ... рддреНрд░реБрдЯрд┐!


 Caused by: io.micronaut.context.exceptions.BeanInstantiationException: Error instantiating bean of type [com.room606.cryptonaut.markets.CryptoMarketDataService] Path Taken: new MarketDataController([CryptoMarketDataService cryptoMarketDataService],FiatExchangeRatesService fiatExchangeRatesService) --> new CryptoMarketDataService([FiatExchangeRatesService fiatExchangeRatesService],MarketDataServiceFactory marketDataServiceFactory) at io.micronaut.context.DefaultBeanContext.doCreateBean(DefaultBeanContext.java:1266) at io.micronaut.context.DefaultBeanContext.createAndRegisterSingleton(DefaultBeanContext.java:1677) at io.micronaut.context.DefaultBeanContext.getBeanForDefinition(DefaultBeanContext.java:1447) at io.micronaut.context.DefaultBeanContext.getBeanInternal(DefaultBeanContext.java:1427) at io.micronaut.context.DefaultBeanContext.getBean(DefaultBeanContext.java:852) at io.micronaut.context.AbstractBeanDefinition.getBeanForConstructorArgument(AbstractBeanDefinition.java:943) ... 36 common frames omitted Caused by: java.lang.NullPointerException: null at com.room606.cryptonaut.markets.FiatExchangeRatesService.<init>(FiatExchangeRatesService.java:20) at com.room606.cryptonaut.markets.$FiatExchangeRatesServiceDefinition.build(Unknown Source) at io.micronaut.context.DefaultBeanContext.doCreateBean(DefaultBeanContext.java:1252) ... 41 common frames omitted 

Micronaut Spring , compile time . , :


 @Value("${cryptonaut.currencies:RUR}") private String supportedCurrencies; private List<String> supportedCounterCurrencies; @PostConstruct void init() { supportedCounterCurrencies = Arrays.asList(supportedCurrencies.split("[,]", -1)); } 

, тАУ javax.annotation.PostConstruct , , , , . .


, , Spring. micronaut @Property Map<String, String> , @Configuration , Random Properties (, ID , , - ) PropertySourceLoader , .. . Spring тАУ ApplicationContext ( xml , web , groovy , ClassPath etc.) , .



, micronaut. Embedded Server feature, Groovy Spock . Java , groovy- . , EmbeddedServer + HttpClient Micronaut API тАФ


 GET /cryptonaut/restapi/portfolio/total.json?fiatCurrency={x} 

API, .


:


 public class PortfolioReportsControllerTest { private static EmbeddedServer server; private static HttpClient client; @Inject private PortfolioService portfolioService; @BeforeClass public static void setupServer() { server = ApplicationContext.run(EmbeddedServer.class); client = server .getApplicationContext() .createBean(HttpClient.class, server.getURL()); } @AfterClass public static void stopServer() { if(server != null) { server.stop(); } if(client != null) { client.stop(); } } @Test public void total() { //TODO: Seems like code smell. I don't like it.. portfolioService = server.getApplicationContext().getBean(PortfolioService.class); Portfolio portfolio = new Portfolio(); Map<String, BigDecimal> coins = new HashMap<>(); BigDecimal amt1 = new BigDecimal("570.05"); BigDecimal amt2 = new BigDecimal("2.5"); coins.put("XRP", amt1); coins.put("QTUM", amt2); portfolio.setCoins(coins); portfolioService.savePortfolio(portfolio); HttpRequest request = HttpRequest.GET("/cryptonaut/restapi/portfolio/total.json?fiatCurrency=USD"); HttpResponse<Total> rsp = client.toBlocking().exchange(request, Total.class); assertEquals(200, rsp.status().getCode()); assertEquals(MediaType.APPLICATION_JSON_TYPE, rsp.getContentType().get()); Total val = rsp.body(); assertEquals("USD", val.getFiatCurrency()); assertEquals(TEST_VALUE.toString(), val.getValue().toString()); assertEquals(amt1.toString(), val.getPortfolio().getCoins().get("XRP").toString()); assertEquals(amt2.toString(), val.getPortfolio().getCoins().get("QTUM").toString()); } } 

, mock PortfolioService.java :


 package com.room606.cryptonaut; import com.room606.cryptonaut.domain.Portfolio; import io.micronaut.context.annotation.Requires; import javax.inject.Singleton; import java.math.BigDecimal; import java.util.Optional; @Singleton @Requires(env="test") public class MockPortfolioService implements PortfolioService { private Portfolio portfolio; public static final BigDecimal TEST_VALUE = new BigDecimal("56.65"); @Override public Portfolio savePortfolio(Portfolio portfolio) { this.portfolio = portfolio; return portfolio; } @Override public Portfolio loadPortfolio() { return portfolio; } @Override public Optional<BigDecimal> calculateTotalValue(Portfolio portfolio, String fiatCurrency) { return Optional.of(TEST_VALUE); } } 

@Requires(env="test") , Application Context . -, micronaut test, , . , , PortfolioServiceImpl @Requires(notEnv="test") . тАУ . Micronaut .


, тАУ , , тАУ mockito . :


 @Test public void priceForUsdDirectRate() throws IOException { when(marketDataServiceFactory.getMarketDataService()).thenReturn(marketDataService); String coinCode = "ETH"; String fiatCurrencyCode = "USD"; BigDecimal priceA = new BigDecimal("218.58"); Ticker targetTicker = new Ticker.Builder().bid(priceA).build(); when(marketDataService.getTicker(new CurrencyPair(new Currency(coinCode), new Currency(fiatCurrencyCode)))).thenReturn(targetTicker); CryptoMarketDataService cryptoMarketDataService = new CryptoMarketDataService(fiatExchangeRatesService, marketDataServiceFactory); assertEquals(priceA, cryptoMarketDataService.getPrice(coinCode, fiatCurrencyCode)); } 


, . . , . , , - IP. , @Cacheable .


Kesh

, . , ( appliction.yml ). redis, Docker- . :
 redis: image: 'bitnami/redis:latest' environment: - ALLOW_EMPTY_PASSWORD=yes ports: - '6379:6379' 

@Cacheable:


 @Cacheable("fiatRates") public Map<String, BigDecimal> getRates() { HttpRequest<?> req = HttpRequest.GET(""); String response = httpClient.retrieve(req, String.class).blockingSingle(); try { JsonNode json = objectReader.readTree(response); String usdPrice = json.get("Valute").get("USD").get("Value").asText(); String eurPrice = json.get("Valute").get("EUR").get("Value").asText(); String gbpPrice = json.get("Valute").get("GBP").get("Value").asText(); Map<String, BigDecimal> prices = new HashMap<>(); prices.put("USD", new BigDecimal(usdPrice)); prices.put("GBP", new BigDecimal(gbpPrice)); prices.put("EUR", new BigDecimal(eurPrice)); return prices; } catch (IOException e) { throw new RuntimeException(e); } } 

application.yml . . :


 caches: fiatrates: expireAfterWrite: "1h" redis: caches: fiatRates: expireAfterWrite: "1h" port: 6379 server: localhost 

:


 #cache redis: uri: localhost:6379 caches: fiatRates: expireAfterWrite: "1h" 

. тАФ тАЬUnexpected error occurred: No cache configured for name: fiatRatesтАЭ:


 ERROR imhsnetty.RoutingInBoundHandler - Unexpected error occurred: No cache configured for name: fiatRates io.micronaut.context.exceptions.ConfigurationException: No cache configured for name: fiatRates at io.micronaut.cache.DefaultCacheManager.getCache(DefaultCacheManager.java:67) at io.micronaut.cache.interceptor.CacheInterceptor.interceptSync(CacheInterceptor.java:176) at io.micronaut.cache.interceptor.CacheInterceptor.intercept(CacheInterceptor.java:128) at io.micronaut.aop.MethodInterceptor.intercept(MethodInterceptor.java:41) at io.micronaut.aop.chain.InterceptorChain.proceed(InterceptorChain.java:147) at com.room606.cryptonaut.markets.clients.$CbrExchangeRatesClientDefinition$Intercepted.getRates(Unknown Source) at com.room606.cryptonaut.markets.FiatExchangeRatesService.getFiatPrice(FiatExchangeRatesService.java:30) at com.room606.cryptonaut.rest.MarketDataController.index(MarketDataController.java:34) at com.room606.cryptonaut.rest.$MarketDataControllerDefinition$$exec2.invokeInternal(Unknown ... 

GitHub - SO . . , . , . boilerplate-, - Redis - , , Spring Boot , .



, Micronaut тАУ , Spring-.


Benchmarking

Disclaimer-: , -, , ( , , , ).

, :


OS: 16.04.1-Ubuntu x86_64 x86_64 x86_64 GNU/Linux
CPU: Intel┬о Core(TM) i7-7700HQ CPU @ 2.80GHz
Mem: 2 8 Gb DDR4, Speed: 2400 MHz
SSD Disk: PCIe NVMe M.2, 256


:


  1. API,
  2. API тАУ тАЬтАЭ .

тАУ Rest Controller тАУ IoC-, .


тАЬ тАЭ :


MicronautSpring Boot
Avg.(ms)2708.42735.2
cryptonaut (ms)1082-

, тАУ 27 Micronaut . , .


?


. , , , тАУ . . Groovy-, , . SO Spring. , , . тАФ . Spring.


:


  • Micronaut тАУ service-discovery, AWS
  • Java. Kotlin Groovy.

.

Source: https://habr.com/ru/post/hi427595/


All Articles