{"id":479265,"date":"2026-05-11T05:57:51","date_gmt":"2026-05-11T05:57:51","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=479265"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=479265","title":{"rendered":"Code Review Horror Stories. \u0427\u0430\u0441\u0442\u044c 2: API, \u043e\u0448\u0438\u0431\u043a\u0438 \u0438 graceful shutdown"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p><em>\u041f\u0440\u043e\u0434\u043e\u043b\u0436\u0435\u043d\u0438\u0435 \u0440\u0430\u0437\u0431\u043e\u0440\u0430 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430 \u0441 \u0441\u043e\u0431\u0435\u0441\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u044f. \u0412 <\/em><a href=\"https:\/\/habr.com\/ru\/articles\/1031010\/\" rel=\"noopener noreferrer nofollow\"><em>\u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438<\/em><\/a><em> \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043b\u0438 8 \u043f\u0440\u043e\u0431\u043b\u0435\u043c concurrency \u0438 memory: race conditions, \u0443\u0442\u0435\u0447\u043a\u0438 \u0433\u043e\u0440\u0443\u0442\u0438\u043d, \u043f\u0440\u043e\u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 mutex, TOCTOU. \u042d\u0442\u043e \u0431\u044b\u043b\u0430 \u043f\u0435\u0440\u0432\u0430\u044f \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u0430 \u0438\u0437 21 \u0431\u0430\u0433\u0430 \u0432 \u043e\u0434\u043d\u043e\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u0435 \u043d\u0430 150 \u0441\u0442\u0440\u043e\u043a.<\/em><\/p>\n<p><em>\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u2014 \u0432\u0442\u043e\u0440\u0430\u044f \u0447\u0430\u0441\u0442\u044c. \u0422\u0443\u0442 \u043d\u0435\u0442 \u0441\u0442\u0440\u0430\u0448\u043d\u044b\u0445 race conditions, \u043d\u043e \u0435\u0441\u0442\u044c \u0442\u043e, \u0447\u0442\u043e \u0432\u044b\u0434\u0430\u0451\u0442 \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u043d\u0430 \u0441\u043e\u0431\u0435\u0441\u0435: <\/em><strong><em>\u043e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u0435 \u043a \u043e\u0448\u0438\u0431\u043a\u0430\u043c, \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f, API design, graceful shutdown, observability<\/em><\/strong><em>. \u042d\u0442\u0438 \u0431\u0430\u0433\u0438 \u043d\u0435 \u0443\u043f\u0430\u0434\u0443\u0442 \u201c\u0432\u0434\u0440\u0443\u0433\u201d \u0432 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0435 \u2014 \u043e\u043d\u0438 \u0431\u0443\u0434\u0443\u0442 \u0442\u0438\u0445\u043e \u043f\u0438\u043b\u0438\u0442\u044c \u0432\u0430\u043c \u043a\u043e\u0441\u0442\u044b\u043b\u044c \u0437\u0430 \u043a\u043e\u0441\u0442\u044b\u043b\u0451\u043c, \u043f\u043e\u043a\u0430 \u043a\u0442\u043e-\u0442\u043e \u043d\u0435 \u0441\u044f\u0434\u0435\u0442 \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c. \u0410\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e \u0434\u043b\u044f Go 1.26.<\/em><\/p>\n<p><em>\u041d\u0430\u043f\u043e\u043c\u043d\u044e \u0438\u0442\u043e\u0433 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438: \u0438\u0437 8 \u0431\u0430\u0433\u043e\u0432 \u043f\u0440\u043e concurrency \u043d\u0430 \u0438\u043d\u0442\u0435\u0440\u0432\u044c\u044e \u043d\u0430\u0448\u0451\u043b 7, \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u043b \u0442\u043e\u043b\u044c\u043a\u043e TOCTOU race. \u0412 \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0438\u0437 13 \u0431\u0430\u0433\u043e\u0432 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u043b <\/em><strong><em>\u0434\u0432\u0430<\/em><\/strong><em>: <\/em><code><em>package applike<\/em><\/code><em> \u0441 <\/em><code><em>func main()<\/em><\/code><em> (\u0442\u043e, \u0447\u0442\u043e \u043a\u043e\u0434 \u043d\u0435 \u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u2014 \u0431\u0430\u043d\u0430\u043b\u044c\u043d\u043e \u043d\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u043b \u043d\u0430 \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0430\u043a\u0435\u0442\u0430) \u0438 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 <\/em><code><em>slog<\/em><\/code><em> (\u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0435 \u0437\u0430\u0446\u0435\u043f\u0438\u043b\u0441\u044f \u0437\u0430 <\/em><code><em>log.Println<\/em><\/code><em>, \u0430 \u0437\u0440\u044f). \u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 11 \u2014 \u043f\u043e\u0439\u043c\u0430\u043b. \u0420\u0430\u0441\u0441\u043a\u0430\u0436\u0443, \u043a\u0430\u043a\u0438\u043c\u0438 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u0430\u043c\u0438 \u0432 \u0447\u0442\u0435\u043d\u0438\u0438 \u043a\u043e\u0434\u0430 \u044f \u0438\u0445 \u0432\u044b\u043b\u0430\u0432\u043b\u0438\u0432\u0430\u043b.<\/em><\/p>\n<h3>\u041d\u0430\u043f\u043e\u043c\u043d\u044e, \u0447\u0442\u043e \u0437\u0430 \u043a\u043e\u0434<\/h3>\n<p>\u041c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441 \u0442\u0440\u0435\u043a\u0438\u043d\u0433\u0430 \u0440\u0435\u043a\u043b\u0430\u043c\u043d\u044b\u0445 \u043a\u043b\u0438\u043a\u043e\u0432 \u043d\u0430 Gin \u0441 in-memory storage. \u041f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 POST \u0441 <code>(user_uuid, campaign_uuid, country)<\/code>, \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 tracking link, \u0434\u0435\u0434\u0443\u043f\u043b\u0438\u0446\u0438\u0440\u0443\u0435\u0442 \u0437\u0430 5 \u0441\u0435\u043a\u0443\u043d\u0434, \u043f\u0430\u0431\u043b\u0438\u0448\u0438\u0442 \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c.<\/p>\n<p>\u0412 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043c\u044b \u043f\u043e\u0447\u0438\u043d\u0438\u043b\u0438 mutex\u2019\u044b, \u0443\u0442\u0435\u0447\u043a\u0438 \u0438 \u043a\u0430\u043d\u0430\u043b\u044b. \u0422\u0435\u043f\u0435\u0440\u044c \u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0430 \u0432\u0441\u0451 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435.<\/p>\n<h3>10. \u201c\u0410 return values \u0443 \u043d\u0430\u0441 \u043a\u0430\u043a, \u043f\u043e \u0444\u044d\u043d-\u0448\u0443\u044e?\u201d<\/h3>\n<p>\u0421\u043c\u043e\u0442\u0440\u044e \u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0443 <code>GetRecent<\/code>:<\/p>\n<pre><code class=\"go\">func (r *InMemoryClickRepository) GetRecent(    userUUID, campaignUUID string,     maxAge time.Duration,) (error, *CampaignClick, bool) {    \/\/ ...}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u0430 \u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0435 \u0433\u043b\u0430\u0437 \u0441\u0430\u043c \u0437\u0430\u0446\u0435\u043f\u0438\u043b\u0441\u044f \u2014 \u043e\u043d\u0430 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f \u043a\u0440\u0438\u0432\u043e, \u043a\u0430\u043a \u0438\u043d\u043e\u0441\u0442\u0440\u0430\u043d\u043d\u044b\u0439 \u0430\u043a\u0446\u0435\u043d\u0442 \u0432 Go-\u043a\u043e\u0434\u0435. \u041e\u0437\u0432\u0443\u0447\u0438\u0432\u0430\u044e: \u201c\u0412 Go \u043a\u043e\u043d\u0432\u0435\u043d\u0446\u0438\u044f \u2014 <code>error<\/code> \u0432\u0441\u0435\u0433\u0434\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439. \u0422\u0443\u0442 \u043e\u043d \u043f\u0435\u0440\u0432\u044b\u0439, \u0438 \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u0435\u0433\u043e \u043d\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 (\u0432\u0435\u0437\u0434\u0435 <code>return nil, ...<\/code>). \u041b\u0438\u0448\u043d\u0438\u0439 \u0441\u043b\u043e\u0442 \u0432 \u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0435 \u2014 \u0432\u044b\u043a\u0438\u043d\u0443\u0442\u044c\u201d.<\/p>\n<p>\u0412 Go \u044d\u0442\u043e \u043d\u0435\u0433\u043b\u0430\u0441\u043d\u0430\u044f \u043a\u043e\u043d\u0432\u0435\u043d\u0446\u0438\u044f: <strong>error \u0432\u0441\u0435\u0433\u0434\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439<\/strong>. \u042d\u0442\u043e \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u0442\u0438\u043b\u0435\u0432\u043e\u0435 \u2014 \u044d\u0442\u043e \u0432\u043b\u0438\u044f\u0435\u0442 \u043d\u0430 \u0447\u0438\u0442\u0430\u0435\u043c\u043e\u0441\u0442\u044c, \u043d\u0430 \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 linter\u2019\u0430\u043c\u0438 (<code>errcheck<\/code> \u043e\u0436\u0438\u0434\u0430\u0435\u0442 error \u0432 \u043a\u043e\u043d\u0446\u0435), \u043d\u0430 \u0442\u043e, \u043a\u0430\u043a \u043b\u044e\u0434\u0438 \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0435 \u0431\u0443\u0434\u0443\u0442 \u044d\u0442\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c. \u041a\u0430\u0436\u0434\u044b\u0439 Go-\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043d\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0435 \u043f\u0438\u0448\u0435\u0442:<\/p>\n<pre><code class=\"go\">val, err := someFunc()<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0410 \u0442\u0443\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f:<\/p>\n<pre><code class=\"go\">err, click, ok := repo.GetRecent(...)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418 \u0442\u044b \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u0441\u043f\u043e\u0442\u044b\u043a\u0430\u0435\u0448\u044c\u0441\u044f. \u041f\u043b\u044e\u0441 \u2014 \u0437\u0430\u0447\u0435\u043c \u0442\u0443\u0442 \u0432\u043e\u043e\u0431\u0449\u0435 <code>error<\/code>? \u0424\u0443\u043d\u043a\u0446\u0438\u044f \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u0435\u0433\u043e \u043d\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 (\u0432\u0435\u0437\u0434\u0435 <code>return nil, ...<\/code>). \u041b\u0438\u0448\u043d\u0438\u0439 \u0441\u043b\u043e\u0442 \u0432 \u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0443\u0442\u0430\u0435\u0442.<\/p>\n<p>\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e:<\/p>\n<pre><code class=\"go\">func (r *InMemoryClickRepository) GetRecent(    userUUID, campaignUUID string,    maxAge time.Duration,) (*CampaignClick, bool) {    \/\/ \u0435\u0441\u043b\u0438 error \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u043d\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u2014 \u0443\u0431\u0438\u0440\u0430\u0435\u043c \u0435\u0433\u043e}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u041c\u043e\u0440\u0430\u043b\u044c:<\/strong> \u041a\u043e\u043d\u0432\u0435\u043d\u0446\u0438\u0438 \u044f\u0437\u044b\u043a\u0430 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0442 \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u0442\u0430\u043a. <code>error<\/code> \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439, <code>context.Context<\/code> \u043f\u0435\u0440\u0432\u044b\u0439, <code>ok bool<\/code> \u0434\u043b\u044f \u0434\u0432\u043e\u0439\u043d\u043e\u0433\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430 \u0441 map\/cast. \u041d\u0430\u0440\u0443\u0448\u0430\u0435\u0448\u044c \u2014 \u0442\u0432\u043e\u0439 \u043a\u043e\u0434 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043a\u0430\u043a \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0439 \u0447\u0435\u043b\u043e\u0432\u0435\u043a\u043e\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 Go \u043d\u0435 \u0437\u043d\u0430\u0435\u0442.<\/p>\n<h3>11. Save() \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 error \u2014 \u0430 \u043c\u044b \u0435\u0433\u043e \u0432\u044b\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c<\/h3>\n<p>\u0421\u043c\u043e\u0442\u0440\u044e \u043d\u0430 handler:<\/p>\n<pre><code class=\"go\">c.repo.Save(click)go c.publisher.Publish(click)ctx.JSON(http.StatusOK, ClickResponse{    ClickUUID:    click.ClickUUID,    TrackingLink: click.TrackingLink,})<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>Save<\/code> \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d \u043a\u0430\u043a <code>func Save(c *CampaignClick) error<\/code>. \u0422\u043e \u0435\u0441\u0442\u044c <strong>\u043f\u043e \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442\u0443<\/strong> \u043c\u043e\u0436\u0435\u0442 \u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0443. \u0410 \u043c\u044b \u0435\u0451 \u043f\u0440\u043e\u0441\u0442\u043e \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u0435\u043c \u0438 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0443 200 OK, \u043a\u0430\u043a \u0431\u0443\u0434\u0442\u043e \u0432\u0441\u0451 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043b\u043e\u0441\u044c.<\/p>\n<p>\u0421\u0435\u0433\u043e\u0434\u043d\u044f in-memory \u2014 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0432\u0441\u0435\u0433\u0434\u0430 <code>nil<\/code>. \u041d\u043e \u0437\u0430\u0432\u0442\u0440\u0430 \u043a\u0442\u043e-\u0442\u043e \u0437\u0430\u043c\u0435\u043d\u0438\u0442 \u043d\u0430 PostgreSQL\/Redis\/Kafka, <code>Save<\/code> \u043d\u0430\u0447\u043d\u0451\u0442 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0435 \u043e\u0448\u0438\u0431\u043a\u0438, \u0430 handler \u043a\u0430\u043a \u043d\u0438 \u0432 \u0447\u0451\u043c \u043d\u0435 \u0431\u044b\u0432\u0430\u043b\u043e \u0440\u0430\u043f\u043e\u0440\u0442\u0443\u0435\u0442 \u201c\u0443\u0441\u043f\u0435\u0445\u201d. \u041a\u043b\u0438\u0435\u043d\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 clickUUID, \u0438\u0434\u0451\u0442 \u043f\u043e tracking link, \u043d\u043e \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0438 \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u0438\u043a\u0430 \u043d\u0435\u0442. Hello, broken funnel.<\/p>\n<p>\u0412 Go 1.26 \u0437\u0430\u0432\u0435\u0437\u043b\u0438 <code><strong>errors.AsType[T]<\/strong><\/code> \u2014 \u0442\u0438\u043f\u043e\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 generic-\u0432\u0430\u0440\u0438\u0430\u043d\u0442 <a href=\"http:\/\/errors.As\" rel=\"noopener noreferrer nofollow\"><code>errors.As<\/code><\/a>. \u0423\u0434\u043e\u0431\u043d\u043e \u0434\u043b\u044f \u0447\u0438\u0441\u0442\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0434\u043e\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u0448\u0438\u0431\u043e\u043a:<\/p>\n<pre><code class=\"go\">if err := c.repo.Save(ctx.Request.Context(), click); err != nil {    if dbErr, ok := errors.AsType[*DBError](err); ok &amp;&amp; dbErr.Retriable {        \/\/ retry \u0438\u043b\u0438 fallback    }    log.Printf(\"save click failed: %v\", err)    ctx.JSON(http.StatusInternalServerError, gin.H{\"error\": \"internal\"})    return}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u041c\u043e\u0440\u0430\u043b\u044c:<\/strong> \u0415\u0441\u043b\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 <code>error<\/code> \u2014 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0439 \u0435\u0433\u043e. \u0412\u0441\u0435\u0433\u0434\u0430. \u0414\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u201c\u0441\u0435\u0439\u0447\u0430\u0441 \u043e\u043d \u0432\u0441\u0435\u0433\u0434\u0430 nil\u201d. \u0421\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0430 \u2014 \u044d\u0442\u043e \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442 \u043d\u0430 \u0431\u0443\u0434\u0443\u0449\u0435\u0435. <code>errcheck<\/code> linter \u0432 CI \u0437\u0430\u043a\u0440\u043e\u0435\u0442 90% \u0442\u0430\u043a\u0438\u0445 \u0434\u044b\u0440 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438.<\/p>\n<h3>12. \u0412\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f? \u041d\u0435, \u043d\u0435 \u0441\u043b\u044b\u0448\u0430\u043b\u0438<\/h3>\n<p>\u0421\u043c\u043e\u0442\u0440\u044e handler:<\/p>\n<pre><code class=\"go\">func (c *ClickController) Post(ctx *gin.Context) {    var req struct {        UserUUID     string `json:\"user_uuid\"`        CampaignUUID string `json:\"campaign_uuid\"`        Country      string `json:\"country\"`    }    _ = ctx.ShouldBindJSON(&amp;req)    \/\/ \u0434\u0430\u043b\u044c\u0448\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c req...}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0417\u0430\u043c\u0435\u0442\u0438\u043b\u0438? <code><em> = ctx.ShouldBindJSON(&amp;req)<\/em><\/code><em>. \u041e\u0448\u0438\u0431\u043a\u0430 \u044f\u0432\u043d\u043e \u043f\u0440\u043e\u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0430 \u0447\u0435\u0440\u0435\u0437 &#171;_ &#171;<\/em>. \u0422\u043e \u0435\u0441\u0442\u044c \u0435\u0441\u043b\u0438 \u043f\u0440\u0438\u0434\u0451\u0442 \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u044b\u0439 JSON, \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043c <strong>\u043f\u0443\u0441\u0442\u0443\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443<\/strong>, \u0430 \u043f\u043e\u0442\u043e\u043c \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0437\u0430\u043f\u0438\u0441\u044c \u0441 \u043f\u0443\u0441\u0442\u044b\u043c\u0438 UUID\u2019\u0430\u043c\u0438. \u0417\u0430\u0442\u0435\u043c \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c tracking link <a href=\"https:\/\/tracker.example\/click?campaign=&amp;user=\" rel=\"noopener noreferrer nofollow\"><code>https:\/\/tracker.example\/click?campaign=&amp;user=<\/code><\/a> \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0443. Hello broken analytics.<\/p>\n<p>\u0418 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u043d\u0435\u0442 \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0438\u043a\u0430\u043a\u043e\u0439. UUID \u043c\u043e\u0433 \u0431\u044b\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u043e\u0439 <code>\"drop table users\"<\/code>, \u0441\u0442\u0440\u0430\u043d\u0430 \u2014 \u043f\u0443\u0441\u0442\u043e\u0439, \u0438\u043b\u0438 <code>\"zz\"<\/code>, \u0438\u043b\u0438 <code>\"\ud83c\uddf7\ud83c\uddfa\"<\/code>. \u0412\u0441\u0451 \u044d\u0442\u043e \u0443\u043b\u0435\u0442\u0438\u0442 \u0432 \u0411\u0414 (\u043a\u043e\u0433\u0434\u0430 in-memory \u0437\u0430\u043c\u0435\u043d\u044f\u0442 \u043d\u0430 \u043f\u043e\u0441\u0442\u0433\u0440\u0435\u0441) \u0438 \u0432 metrics.<\/p>\n<p>\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u2014 Gin \u0443\u043c\u0435\u0435\u0442 \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0430\u043c \u0447\u0435\u0440\u0435\u0437 <code>binding<\/code> \u0442\u0435\u0433\u0438:<\/p>\n<pre><code class=\"go\">func (c *ClickController) Post(ctx *gin.Context) {    var req struct {        UserUUID     string `json:\"user_uuid\"     binding:\"required,uuid\"`        CampaignUUID string `json:\"campaign_uuid\" binding:\"required,uuid\"`        Country      string `json:\"country\"       binding:\"required,len=2,alpha\"`    }    if err := ctx.ShouldBindJSON(&amp;req); err != nil {        ctx.JSON(http.StatusBadRequest, gin.H{\"error\": err.Error()})        return    }    \/\/ \u0434\u0430\u043b\u044c\u0448\u0435 \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0432\u0435\u0440\u044f\u0442\u044c req}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u041c\u043e\u0440\u0430\u043b\u044c:<\/strong> \u041b\u044e\u0431\u043e\u0439 external input \u2014 \u044d\u0442\u043e <strong>\u0432\u0440\u0430\u0436\u0434\u0435\u0431\u043d\u0430\u044f \u0441\u0440\u0435\u0434\u0430<\/strong>. \u0412\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0439 \u043d\u0430 \u0433\u0440\u0430\u043d\u0438\u0446\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b. \u0418 \u041d\u0418\u041a\u041e\u0413\u0414\u0410 \u043d\u0435 \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u0439 \u043e\u0448\u0438\u0431\u043a\u0438 \u0447\u0435\u0440\u0435\u0437 &#171;_&#187;<em>. \u042d<\/em>\u0442\u043e \u0432\u0441\u0435\u0433\u0434\u0430 \u0441\u0440\u0430\u0437\u0443 \u0442\u0440\u0438\u0433\u0433\u0435\u0440 \u043d\u0430 code review: \u043b\u0438\u0431\u043e \u044f\u0432\u043d\u043e \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u043e\u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0430 \u0438 \u044d\u0442\u043e \u041e\u041a (\u0438 \u0442\u043e\u0433\u0434\u0430 \u043d\u0443\u0436\u0435\u043d \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439 \u2014 \u043f\u043e\u0447\u0435\u043c\u0443), \u043b\u0438\u0431\u043e \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u043e\u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0430 \u0438 \u044d\u0442\u043e <strong>\u041d\u0415<\/strong> \u041e\u041a. \u0422\u0440\u0435\u0442\u044c\u0435\u0433\u043e \u043d\u0435 \u0434\u0430\u043d\u043e.<\/p>\n<h3>13. Country \u0431\u0435\u0437 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043d\u0430 ISO 3166-1<\/h3>\n<p>\u0414\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c <code>binding:\"len=2,alpha\"<\/code>, \u044d\u0442\u043e \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442 <code>\"XQ\"<\/code>, <code>\"ZZ\"<\/code>, <code>\"AA\"<\/code> \u2014 \u0444\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e 2 \u0431\u0443\u043a\u0432\u044b, \u043d\u043e \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u043a\u043e\u0434\u044b \u0441\u0442\u0440\u0430\u043d. \u0412 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0435 \u043f\u043e\u0442\u043e\u043c \u044d\u0442\u0438 \u201c\u0441\u0442\u0440\u0430\u043d\u044b\u201d \u0441\u0438\u0434\u044f\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u043e\u0439 \u0438 \u043f\u043e\u0440\u0442\u044f\u0442 \u0433\u0440\u0430\u0444\u0438\u043a\u0438.<\/p>\n<p>\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u2014 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0439 validator \u0441 whitelist\u2019\u043e\u043c:<\/p>\n<pre><code class=\"go\">import \"github.com\/biter777\/countries\"func validateCountry(fl validator.FieldLevel) bool {    code := fl.Field().String()    return countries.ByName(code).IsValid()}\/\/ \u043f\u0440\u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438:if v, ok := binding.Validator.Engine().(*validator.Validate); ok {    v.RegisterValidation(\"iso3166\", validateCountry)}\/\/ \u0432 struct'\u0435:Country string `binding:\"required,iso3166\"`<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041b\u0438\u0431\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c enum\u2019\u043e\u043c \u0438 \u043f\u0430\u0440\u0441\u0438\u0442\u044c:<\/p>\n<pre><code class=\"go\">type CountryCode stringfunc (c *CountryCode) UnmarshalJSON(data []byte) error {    var s string    if err := json.Unmarshal(data, &amp;s); err != nil {        return err    }    if !isValidISO3166(s) {        return fmt.Errorf(\"invalid country code: %q\", s)    }    *c = CountryCode(s)    return nil}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u041c\u043e\u0440\u0430\u043b\u044c:<\/strong> \u201c\u042d\u0442\u043e \u0436\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u0442\u0440\u043e\u043a\u0430\u201d \u2014 \u0433\u043b\u0430\u0432\u043d\u044b\u0439 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u043c\u0443\u0441\u043e\u0440\u0430 \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u0445. \u0415\u0441\u043b\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438\u0437 \u043a\u043e\u043d\u0435\u0447\u043d\u043e\u0433\u043e \u043d\u0430\u0431\u043e\u0440\u0430 \u2014 \u0437\u0430\u0432\u0435\u0434\u0438 enum \u0438\u043b\u0438 \u0432\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440. Domain types &gt; primitive types.<\/p>\n<h3>14. Tracking link \u0441\u043e\u0431\u0440\u0430\u043d \u0441\u0442\u0440\u043e\u043a\u043e\u0439 \u2014 \u0430 \u0442\u0430\u043c \u0431\u0430\u0433<\/h3>\n<p>\u0412\u043d\u0438\u043c\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0441\u043c\u043e\u0442\u0440\u044e \u043d\u0430:<\/p>\n<pre><code class=\"go\">func generateTrackingLink(campaignUUID, userUUID string) string {    return \"https:\/\/tracker.example\/click?campaign=\" + campaignUUID + \"?user=\" + userUUID}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412\u0438\u0434\u0438\u0442\u0435? <strong>\u0414\u0432\u0430 \u0437\u043d\u0430\u043a\u0430 <\/strong><code><strong>?<\/strong><\/code><strong> \u0432 URL.<\/strong> \u0414\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c <code>?<\/code> \u0434\u043b\u044f \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u0438 <code>&amp;<\/code> \u0434\u043b\u044f \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0445. \u0422\u043e \u0435\u0441\u0442\u044c \u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0432 <code>?campaign=...?user=...<\/code> \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 <code>campaign<\/code> \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 <code>&lt;uuid&gt;?user=&lt;uuid&gt;<\/code>, \u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 <code>user<\/code> \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 tracker\u2019\u0430.<\/p>\n<p>\u042d\u0442\u043e \u0434\u0430\u0436\u0435 \u043d\u0435 \u0431\u0430\u0433 concurrency \u0438\u043b\u0438 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438. \u042d\u0442\u043e <strong>\u0431\u0438\u0437\u043d\u0435\u0441-\u0431\u0430\u0433<\/strong>: \u0442\u0440\u0435\u043a\u0435\u0440 \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u0442 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043b\u0438\u043a \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e. \u0414\u0435\u043d\u044c\u0433\u0438 \u0440\u0435\u043a\u043b\u0430\u043c\u043e\u0434\u0430\u0442\u0435\u043b\u0435\u0439 \u0443\u043b\u0435\u0442\u0430\u044e\u0442 \u0432 \u043d\u0438\u043a\u0443\u0434\u0430, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0430 \u0431\u0438\u0442\u0430\u044f.<\/p>\n<p><strong>\u041c\u043e\u0440\u0430\u043b\u044c:<\/strong> \u041d\u0435 \u043a\u043b\u0435\u0439 URL\u2019\u044b \u0438 SQL-\u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0441\u0442\u0440\u043e\u043a\u0430\u043c\u0438. \u0414\u043b\u044f URL \u2014 <code>net\/url<\/code>, \u0434\u043b\u044f SQL \u2014 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b. \u042d\u0442\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u043e 1990-\u0445, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043f\u043e\u0447\u0435\u043c\u0443-\u0442\u043e \u0432\u0441\u0451 \u0435\u0449\u0451 \u043d\u0430\u0440\u0443\u0448\u0430\u044e\u0442 \u0432 2026-\u043c.<\/p>\n<h3>15. URL \u0431\u0435\u0437 escaping \u2014 open \u0434\u043b\u044f injection<\/h3>\n<p>\u0414\u043e\u043f\u0443\u0441\u0442\u0438\u043c, \u043c\u044b \u043f\u043e\u0447\u0438\u043d\u0438\u043b\u0438 \u043f\u0440\u043e\u0448\u043b\u044b\u0439 \u0431\u0430\u0433 \u0438 \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u043c URL \u0440\u0443\u043a\u0430\u043c\u0438: <code>?campaign=<\/code> + <code>&amp;user=<\/code>. \u0412\u0441\u0451 \u0435\u0449\u0451 \u043f\u043b\u043e\u0445\u043e: <strong>\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043d\u0435 \u044d\u043a\u0440\u0430\u043d\u0438\u0440\u0443\u044e\u0442\u0441\u044f<\/strong>. \u0415\u0441\u043b\u0438 \u0432 <code>userUUID<\/code> \u043f\u0440\u0438\u043b\u0435\u0442\u0438\u0442 \u0441\u0442\u0440\u043e\u043a\u0430 \u0441 <code>&amp;<\/code> \u0438\u043b\u0438 <code>=<\/code> (\u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u0442 \u0441\u0430\u043c\u044b\u0439 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u0432\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440 \u0438\u0437 \u0431\u0430\u0433\u0430 #12), \u043c\u044b \u0441\u043b\u043e\u043c\u0430\u0435\u043c \u0440\u0430\u0437\u0431\u043e\u0440 query string \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 tracker\u2019\u0430:<\/p>\n<pre><code>?campaign=abc&amp;user=evil&amp;fake=injected<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 <code>user<\/code> \u0441\u0442\u0430\u043b <code>evil<\/code>, \u0430 \u0432 URL \u043f\u043e\u044f\u0432\u0438\u043b\u0441\u044f \u043b\u0435\u0432\u044b\u0439 <code>fake<\/code>. \u0415\u0441\u043b\u0438 tracker \u044d\u0442\u043e\u043c\u0443 \u0434\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u2014 \u0443 \u043d\u0430\u0441 injection.<\/p>\n<p>\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u2014 <code>net\/url<\/code>:<\/p>\n<pre><code class=\"go\">import \"net\/url\"func generateTrackingLink(campaignUUID, userUUID string) string {    u, _ := url.Parse(\"https:\/\/tracker.example\/click\")    q := u.Query()    q.Set(\"campaign\", campaignUUID)    q.Set(\"user\", userUUID)    u.RawQuery = q.Encode()  \/\/ \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 escape    return u.String()}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>url.Values.Encode()<\/code> \u0441\u0434\u0435\u043b\u0430\u0435\u0442 URL-escaping \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438. \u0417\u0430\u043e\u0434\u043d\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u0440\u0430\u0441\u0441\u0442\u0430\u0432\u0438\u0442 <code>?<\/code> \u0438 <code>&amp;<\/code>.<\/p>\n<p><strong>\u041c\u043e\u0440\u0430\u043b\u044c:<\/strong> \u041b\u044e\u0431\u044b\u0435 user-controlled \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f, \u043f\u043e\u043f\u0430\u0434\u0430\u044e\u0449\u0438\u0435 \u0432 URL\/SQL\/HTML \u2014 \u0434\u043e\u043b\u0436\u043d\u044b \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 escape. <code>net\/url<\/code>, <code>database\/sql<\/code> (placeholders), <code>html\/template<\/code> (\u0430 \u043d\u0435 <code>text\/template<\/code>) \u2014 \u044d\u0442\u043e \u0431\u0430\u0437\u043e\u0432\u0430\u044f \u0433\u0438\u0433\u0438\u0435\u043d\u0430. \u041d\u0435 \u0434\u0435\u043b\u0430\u0439 \u0440\u0443\u043a\u0430\u043c\u0438 \u0442\u043e, \u0447\u0442\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u0430\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u0435\u043b\u0430\u0435\u0442 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e.<\/p>\n<h3>16. Status 200 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0440\u0435\u0441\u0443\u0440\u0441\u0430<\/h3>\n<p>Handler \u0432\u0441\u0435\u0433\u0434\u0430 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 <code>http.StatusOK<\/code>:<\/p>\n<pre><code class=\"go\">ctx.JSON(http.StatusOK, ClickResponse{...})<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418 \u0434\u043b\u044f <strong>\u043d\u043e\u0432\u043e\u0433\u043e<\/strong> \u043a\u043b\u0438\u043a\u0430, \u0438 \u0434\u043b\u044f <strong>\u0434\u0435\u0434\u0443\u043f\u043b\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e<\/strong> \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430 \u0438\u0437 \u043a\u0435\u0448\u0430. \u0421\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u044d\u0442\u043e \u043d\u0435\u0432\u0435\u0440\u043d\u043e:<\/p>\n<ul>\n<li>\n<p>\u041d\u043e\u0432\u044b\u0439 \u043a\u043b\u0438\u043a \u2192 \u0441\u043e\u0437\u0434\u0430\u043d \u0440\u0435\u0441\u0443\u0440\u0441 \u2192 <code>201 Created<\/code><\/p>\n<\/li>\n<li>\n<p>\u0414\u0435\u0434\u0443\u043f\u043b\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u2192 \u0441\u0442\u0430\u0440\u044b\u0439 \u0440\u0435\u0441\u0443\u0440\u0441 \u2192 <code>200 OK<\/code> (\u0438\u043b\u0438 \u0434\u0430\u0436\u0435 <code>409 Conflict<\/code>, \u0435\u0441\u043b\u0438 \u0445\u043e\u0442\u0438\u043c \u044f\u0432\u043d\u043e \u0441\u0438\u0433\u043d\u0430\u043b\u0438\u0442\u044c \u0434\u0443\u0431\u043b\u044c)<\/p>\n<\/li>\n<\/ul>\n<p>\u041a\u043b\u0438\u0435\u043d\u0442\u0443 \u043e\u0431\u044b\u0447\u043d\u043e \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e (\u043e\u043d \u0441\u043c\u043e\u0442\u0440\u0438\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 <code>2xx<\/code>), \u043d\u043e \u043a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0442\u0435\u043b\u0435\u0436\u043a\u0430 observability \u2014 \u0433\u0440\u0430\u0444\u0438\u043a\u0438 \u201c\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043d\u043e\u0432\u044b\u0445 vs \u0434\u0443\u0431\u043b\u0438\u043a\u0430\u0442\u043e\u0432\u201d \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043d\u0435\u043b\u044c\u0437\u044f \u0431\u0435\u0437 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u043c\u0435\u0442\u0440\u0438\u043a\u0438, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e HTTP-\u043b\u043e\u0433\u0438 \u043d\u0435 \u0440\u0430\u0437\u043b\u0438\u0447\u0430\u044e\u0442 \u044d\u0442\u0438 \u0441\u043b\u0443\u0447\u0430\u0438.<\/p>\n<p>\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e:<\/p>\n<pre><code class=\"go\">if cached, ok := c.repo.GetRecent(...); ok {    ctx.JSON(http.StatusOK, toResponse(cached))    return}\/\/ ... \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0433\u043ectx.JSON(http.StatusCreated, toResponse(click))<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u041c\u043e\u0440\u0430\u043b\u044c:<\/strong> HTTP status codes \u2014 \u044d\u0442\u043e API. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439 \u0438\u0445 \u043f\u043e \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044e. <code>201 Created<\/code> \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f, <code>204 No Content<\/code> \u0434\u043b\u044f DELETE \u0431\u0435\u0437 \u0442\u0435\u043b\u0430, <code>409 Conflict<\/code> \u0434\u043b\u044f \u0434\u0443\u0431\u043b\u0438\u043a\u0430\u0442\u043e\u0432. \u042d\u0442\u043e \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u0430\u044f \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f \u0434\u043b\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u0438 \u043c\u0435\u0442\u0440\u0438\u043a\u0430 \u0434\u043b\u044f observability.<\/p>\n<h3>17. Context? \u041a\u0430\u043a\u043e\u0439 context?<\/h3>\n<p>\u0413\u0434\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0436\u0438\u0437\u043d\u0435\u043d\u043d\u044b\u043c \u0446\u0438\u043a\u043b\u043e\u043c? \u041d\u0438\u0433\u0434\u0435. \u041c\u0435\u0442\u043e\u0434 <code>Save<\/code> \u043d\u0435 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 <code>context.Context<\/code>. <code>Publish<\/code> \u0442\u043e\u0436\u0435. \u0415\u0441\u043b\u0438 \u0437\u0430\u043f\u0440\u043e\u0441 \u043e\u0442\u043c\u0435\u043d\u0438\u043b\u0441\u044f (\u043a\u043b\u0438\u0435\u043d\u0442 \u043e\u0442\u0432\u0430\u043b\u0438\u043b\u0441\u044f) \u2014 \u043c\u044b \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u043a\u043b\u0438\u043a \u0438 \u043f\u043e\u043f\u044b\u0442\u0430\u0435\u043c\u0441\u044f \u043e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u0442\u044c.<\/p>\n<p>\u0414\u043b\u044f in-memory \u044d\u0442\u043e \u043d\u0435 \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u043e. \u041d\u043e \u043a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0437\u0430\u043c\u0435\u043d\u044f\u0442 \u043d\u0430 PostgreSQL \u2014 <code>db.Exec(...)<\/code> \u0431\u0435\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u0431\u0443\u0434\u0435\u0442 \u0432\u0438\u0441\u0435\u0442\u044c \u0434\u043e \u043f\u043e\u0431\u0435\u0434\u043d\u043e\u0433\u043e, \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u043a\u043b\u0438\u0435\u043d\u0442 \u0443\u0436\u0435 \u0443\u0448\u0451\u043b. \u0420\u0435\u0441\u0443\u0440\u0441\u044b DB connection pool \u043a\u043e\u043d\u0447\u0430\u0442\u0441\u044f, \u0441\u0435\u0440\u0432\u0438\u0441 \u043b\u044f\u0436\u0435\u0442.<\/p>\n<p>\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430\u044f \u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0430:<\/p>\n<pre><code class=\"go\">func (r *InMemoryClickRepository) Save(ctx context.Context, c *CampaignClick) error {    select {    case &lt;-ctx.Done():        return ctx.Err()    default:    }    r.mu.Lock()    defer r.mu.Unlock()    r.clicks = append(r.clicks, c)    return nil}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418 \u0432 handler\u2019\u0435:<\/p>\n<pre><code class=\"go\">if err := c.repo.Save(ctx.Request.Context(), click); err != nil {    \/\/ ...}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u041c\u043e\u0440\u0430\u043b\u044c:<\/strong> <code>context.Context<\/code> \u2014 \u043f\u0435\u0440\u0432\u044b\u0439 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442 \u043b\u044e\u0431\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0434\u0435\u043b\u0430\u0435\u0442 I\/O \u0438\u043b\u0438 \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u044f\u0442\u044c \u0437\u0430\u043c\u0435\u0442\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f. \u0414\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0441\u0435\u0439\u0447\u0430\u0441 \u043e\u043d\u043e \u201c\u0431\u044b\u0441\u0442\u0440\u043e\u201d \u2014 \u0437\u0430\u0432\u0442\u0440\u0430 \u043a\u0442\u043e-\u0442\u043e \u0437\u0430\u043c\u0435\u043d\u0438\u0442 in-memory \u043d\u0430 \u0441\u0435\u0442\u0435\u0432\u043e\u0439 \u0432\u044b\u0437\u043e\u0432, \u0438 \u0431\u0435\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0431\u043e\u043c\u0431\u0430.<\/p>\n<h3>18. \u041d\u0435\u0442 graceful shutdown \u2014 \u0442\u0435\u0440\u044f\u0435\u043c in-flight \u0437\u0430\u043f\u0440\u043e\u0441\u044b<\/h3>\n<p>\u0421\u043c\u043e\u0442\u0440\u044e \u0432 <code>main<\/code>:<\/p>\n<pre><code class=\"go\">r := gin.Default()r.POST(\"\/v1\/campaign\/click\", ctrl.Post)if err := r.Run(\":8080\"); err != nil {    panic(err)}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><a href=\"http:\/\/r.Run\" rel=\"noopener noreferrer nofollow\"><code>r.Run<\/code><\/a><code>()<\/code> \u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0435\u0442 main-\u0433\u043e\u0440\u0443\u0442\u0438\u043d\u0443 \u0434\u043e \u043e\u0448\u0438\u0431\u043a\u0438. \u041a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0434\u0451\u0442 SIGTERM (\u0430 \u044d\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0434\u0435\u043f\u043b\u043e\u0435 \u0432 Kubernetes), \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043f\u0440\u043e\u0441\u0442\u043e <strong>\u0443\u0431\u044c\u0451\u0442\u0441\u044f<\/strong>. \u0412\u0441\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u044b\u043b\u0438 \u0432 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u2014 \u043f\u043e\u0442\u0435\u0440\u044f\u044e\u0442\u0441\u044f. \u0414\u0435\u0434\u0443\u043f-\u043a\u0435\u0448 \u0432 \u043f\u0430\u043c\u044f\u0442\u0438 \u2014 \u043f\u043e\u0442\u0435\u0440\u044f\u0435\u0442\u0441\u044f. \u041a\u0430\u043d\u0430\u043b <code>publisher.out<\/code> \u0441 \u043d\u0430\u043a\u043e\u043f\u043b\u0435\u043d\u043d\u044b\u043c\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438 \u2014 \u043f\u043e\u0442\u0435\u0440\u044f\u0435\u0442\u0441\u044f.<\/p>\n<p>\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u2014 \u0447\u0435\u0440\u0435\u0437 <code>http.Server<\/code> \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u0438 signal handling:<\/p>\n<pre><code class=\"go\">srv := &amp;http.Server{    Addr:              \":8080\",    Handler:           r,    ReadHeaderTimeout: 5 * time.Second,  \/\/ \u0437\u0430\u0449\u0438\u0442\u0430 \u043e\u0442 Slowloris    ReadTimeout:       30 * time.Second,    WriteTimeout:      30 * time.Second,    IdleTimeout:       120 * time.Second,}go func() {    if err := srv.ListenAndServe(); err != nil &amp;&amp; !errors.Is(err, http.ErrServerClosed) {        log.Fatalf(\"server: %v\", err)    }}()\/\/ \u0416\u0434\u0451\u043c \u0441\u0438\u0433\u043d\u0430\u043bquit := make(chan os.Signal, 1)signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)&lt;-quit\/\/ \u0414\u0430\u0451\u043c 30 \u0441\u0435\u043a\u0443\u043d\u0434 \u043d\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 in-flight \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)defer cancel()if err := srv.Shutdown(ctx); err != nil {    log.Printf(\"forced shutdown: %v\", err)}\/\/ \u0418 \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0435\u043f\u0435\u0440\u044c \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u043c publisher, repository \u0438 \u0442.\u0434.publisher.Close(ctx)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u041c\u043e\u0440\u0430\u043b\u044c:<\/strong> \u0412 production graceful shutdown \u2014 \u044d\u0442\u043e \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e, \u043d\u0435 \u201cnice to have\u201d. \u0411\u0435\u0437 \u043d\u0435\u0433\u043e \u043a\u0430\u0436\u0434\u044b\u0439 \u0434\u0435\u043f\u043b\u043e\u0439 = \u043f\u043e\u0442\u0435\u0440\u044f\u043d\u043d\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b = \u043d\u0435\u0434\u043e\u0432\u043e\u043b\u044c\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438.<\/p>\n<h3>19. HTTP timeout\u2019\u044b \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u2014 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0442<\/h3>\n<p>\u0417\u0430\u043c\u0435\u0442\u0438\u043b\u0438 \u0432 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0432\u044b\u0448\u0435 \u0447\u0435\u0442\u044b\u0440\u0435 \u043f\u043e\u043b\u044f? <code>ReadHeaderTimeout<\/code>, <code>ReadTimeout<\/code>, <code>WriteTimeout<\/code>, <code>IdleTimeout<\/code>. \u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0443 <code>http.Server<\/code> <strong>\u043e\u043d\u0438 \u0432\u0441\u0435 \u0440\u0430\u0432\u043d\u044b \u043d\u0443\u043b\u044e<\/strong> = \u201c\u0431\u0435\u0437 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0439\u201d. \u042d\u0442\u043e \u0437\u043d\u0430\u043c\u0435\u043d\u0438\u0442\u0430\u044f \u043b\u043e\u0432\u0443\u0448\u043a\u0430 Go.<\/p>\n<p>\u0427\u0442\u043e \u044d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442 \u043d\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435:<\/p>\n<ul>\n<li>\n<p>\u041e\u0434\u0438\u043d \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u044b\u0439 \u043a\u043b\u0438\u0435\u043d\u0442 \u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0431\u0435\u0441\u043a\u043e\u043d\u0435\u0447\u043d\u043e (\u0430\u0442\u0430\u043a\u0430 <strong>Slowloris<\/strong>)<\/p>\n<\/li>\n<li>\n<p>\u041a\u0430\u0436\u0434\u043e\u0435 \u0442\u0430\u043a\u043e\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u2014 \u0437\u0430\u043d\u044f\u0442\u044b\u0439 worker<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430 N \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u043b\u043e\u0436\u0438\u0442\u0441\u044f \u043f\u0440\u0438 N == file descriptor limit<\/p>\n<\/li>\n<\/ul>\n<p><code>gin.Default().Run(...)<\/code> \u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c \u0442\u043e\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 <code>http.Server<\/code> \u0431\u0435\u0437 \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u043e\u0432. \u042d\u0442\u043e \u0447\u0430\u0441\u0442\u044c \u0431\u0430\u0433\u0430 #18, \u043d\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u2014 \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043d\u0430 \u0441\u043e\u0431\u0435\u0441\u0435 \u043f\u0440\u043e \u044d\u0442\u043e \u0441\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u044e\u0442 \u0432\u043d\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 shutdown.<\/p>\n<p>\u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u043d\u0444\u0438\u0433 \u0434\u043b\u044f production:<\/p>\n<pre><code class=\"go\">srv := &amp;http.Server{    ReadHeaderTimeout: 5 * time.Second,   \/\/ \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u2014 \u0437\u0430\u0449\u0438\u0442\u0430 \u043e\u0442 Slowloris    ReadTimeout:       30 * time.Second,  \/\/ \u043f\u043e\u043b\u043d\u044b\u0439 read \u0441 body    WriteTimeout:      30 * time.Second,  \/\/ \u0437\u0430\u043f\u0438\u0441\u044c \u043e\u0442\u0432\u0435\u0442\u0430    IdleTimeout:       120 * time.Second, \/\/ keep-alive}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0431\u044b\u0432\u0430\u044e\u0442 \u0434\u043e\u043b\u0433\u0438\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b (\u0441\u0442\u0440\u0438\u043c\u0438\u043d\u0433, \u0431\u043e\u043b\u044c\u0448\u043e\u0439 upload) \u2014 <code>ReadTimeout<\/code>\/<code>WriteTimeout<\/code> \u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u043e\u0431\u043e\u043b\u044c\u0448\u0435 \u0438\u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 <code>http.ResponseController<\/code> \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f per-request.<\/p>\n<p><strong>\u041c\u043e\u0440\u0430\u043b\u044c:<\/strong> <code>http.Server{}<\/code> \u0441 \u0434\u0435\u0444\u043e\u043b\u0442\u0430\u043c\u0438 \u2014 \u044d\u0442\u043e <strong>\u0431\u043e\u043c\u0431\u0430 \u0437\u0430\u043c\u0435\u0434\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f<\/strong>. \u041c\u0438\u043d\u0438\u043c\u0443\u043c <code>ReadHeaderTimeout<\/code> \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0432\u0441\u0435\u0433\u0434\u0430. \u042d\u0442\u043e \u0437\u043d\u0430\u044e\u0442 \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c \u0441\u043e\u0431\u0435\u0441\u0435 \u043d\u0430 Senior, \u0438 \u043d\u0435 \u0437\u043d\u0430\u0442\u044c \u043f\u0440\u043e \u044d\u0442\u043e \u2014 \u0441\u0442\u044b\u0434\u043d\u043e.<\/p>\n<h3>20. log.Println \u2014 \u0437\u0430\u0431\u044b\u043b\u0438, \u0447\u0442\u043e \u0435\u0441\u0442\u044c slog \u2014 \u0442\u0440\u0435\u0442\u0438\u0439 \u043f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0439<\/h3>\n<p>\u042d\u0442\u043e\u0442 \u044f \u0442\u043e\u0436\u0435 \u043f\u0440\u043e\u043c\u0430\u0445\u043d\u0443\u043b \u043d\u0430 \u0438\u043d\u0442\u0435\u0440\u0432\u044c\u044e. \u041f\u043e \u0432\u0441\u0435\u043c\u0443 \u043a\u043e\u0434\u0443:<\/p>\n<pre><code class=\"go\">log.Println(\"post error:\", err)log.Println(\"click:\", string(bodyBytes))<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0413\u043b\u0430\u0437 \u0441\u043a\u043e\u043b\u044c\u0437\u043d\u0443\u043b \u043f\u043e <code>log.Println<\/code> \u043a\u0430\u043a \u043f\u043e \u0447\u0435\u043c\u0443-\u0442\u043e \u043d\u0435\u0439\u0442\u0440\u0430\u043b\u044c\u043d\u043e\u043c\u0443 \u2014 \u043c\u043e\u043b, \u043d\u0443 \u043b\u043e\u0433\u0438\u0440\u0443\u0435\u0442 \u0438 \u043b\u043e\u0433\u0438\u0440\u0443\u0435\u0442. \u0410 \u0437\u0440\u044f. \u0423\u0436\u0435 \u043f\u0440\u0438 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0435 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u0441\u0442\u0430, \u043f\u0435\u0440\u0435\u0447\u0438\u0442\u044b\u0432\u0430\u044f, \u0441\u0440\u0430\u0437\u0443 \u0431\u0440\u043e\u0441\u0438\u043b\u043e\u0441\u044c: 2026 \u0433\u043e\u0434, \u0432 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0435 \u0434\u0430\u0432\u043d\u043e <code>log\/slog<\/code>, \u0430 \u0442\u0443\u0442 \u043d\u0438\u0447\u0435\u043c \u043d\u0435 \u043b\u0443\u0447\u0448\u0435 <code>fmt.Println<\/code>-\u043e\u0431\u0451\u0440\u0442\u043a\u0438.<\/p>\n<p>\u042d\u0442\u043e \u043f\u043b\u043e\u0445\u043e \u043f\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c \u043f\u0440\u0438\u0447\u0438\u043d\u0430\u043c:<\/p>\n<ol>\n<li>\n<p><strong>\u041d\u0435 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043e<\/strong> \u2014 \u0432 production \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 (Loki, Datadog, ELK) \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u0442 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e \u043f\u0430\u0440\u0441\u0438\u0442\u044c<\/p>\n<\/li>\n<li>\n<p><strong>\u041d\u0435\u0442 \u0443\u0440\u043e\u0432\u043d\u0435\u0439<\/strong> \u2014 \u043d\u0435\u043b\u044c\u0437\u044f \u043e\u0442\u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432\u0430\u0442\u044c DEBUG \u043e\u0442 ERROR<\/p>\n<\/li>\n<li>\n<p><strong>\u041d\u0435\u0442 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430<\/strong> \u2014 \u043d\u0435\u0442 request_id, trace_id, user_id<\/p>\n<\/li>\n<li>\n<p><strong>\u0413\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u0439 logger<\/strong> \u2014 \u043d\u0435\u043b\u044c\u0437\u044f \u043f\u043e\u0434\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0430 \u0442\u0435\u0441\u0442\u0430\u0445<\/p>\n<\/li>\n<\/ol>\n<p>\u0421\u043e Go 1.21 \u0432 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0435 \u0435\u0441\u0442\u044c <code><strong>log\/slog<\/strong><\/code> \u2014 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043b\u043e\u0433\u0433\u0435\u0440. \u0412 Go 1.26 \u043e\u043d \u0441\u0442\u0430\u043b \u0435\u0449\u0451 \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u0435\u0435.<\/p>\n<p>\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e:<\/p>\n<pre><code class=\"go\">import \"log\/slog\"logger := slog.New(slog.NewJSONHandler(os.Stdout, &amp;slog.HandlerOptions{    Level: slog.LevelInfo,}))logger.Error(\"publish failed\",    slog.String(\"click_uuid\", click.ClickUUID),    slog.String(\"user_uuid\", click.UserUUID),    slog.Any(\"err\", err),)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>Output:<\/p>\n<pre><code class=\"json\">{\"time\":\"2026-05-04T12:00:00Z\",\"level\":\"ERROR\",\"msg\":\"publish failed\",\"click_uuid\":\"...\",\"user_uuid\":\"...\",\"err\":\"...\"}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u041c\u043e\u0440\u0430\u043b\u044c:<\/strong> \u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u0442\u0435 \u043d\u0430 <code>log\/slog<\/code>. <code>log.Println<\/code> \u0445\u043e\u0440\u043e\u0448 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f quick\u2019n\u2019dirty CLI-\u0443\u0442\u0438\u043b\u0438\u0442. \u0412 \u0441\u0435\u0440\u0432\u0438\u0441\u0435 \u2014 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043b\u043e\u0433\u0438 \u0441 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043e\u043c, \u0432\u0441\u0435\u0433\u0434\u0430.<\/p>\n<h3>21. \u0422\u0435\u0441\u0442\u043e\u0432\u0430\u044f \u0433\u043e\u0440\u0443\u0442\u0438\u043d\u0430 \u0431\u0435\u0441\u043f\u043e\u043b\u0435\u0437\u043d\u0430 \u2014 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 random UUIDs<\/h3>\n<p>\u0421\u0430\u043c\u044b\u0439 \u0432\u0438\u0448\u0435\u043d\u043a\u043e-\u043d\u0430-\u0442\u043e\u0440\u0442\u0435 \u0431\u0430\u0433. \u0412 <code>main()<\/code> \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0444\u043e\u043d\u043e\u0432\u0430\u044f \u0433\u043e\u0440\u0443\u0442\u0438\u043d\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043a\u0430\u0436\u0434\u0443\u044e \u0441\u0435\u043a\u0443\u043d\u0434\u0443 \u0448\u043b\u0451\u0442 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441:<\/p>\n<pre><code class=\"go\">go func() {    ticker := time.NewTicker(1 * time.Second)    defer ticker.Stop()    for range ticker.C {        payload := struct {            UserUUID     string `json:\"user_uuid\"`            CampaignUUID string `json:\"campaign_uuid\"`            Country      string `json:\"country\"`        }{            UserUUID:     uuid.NewString(),  \/\/ \u274c \u043d\u043e\u0432\u044b\u0439 UUID \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437!            CampaignUUID: uuid.NewString(),  \/\/ \u274c \u043d\u043e\u0432\u044b\u0439 UUID \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437!            Country:      \"US\",        }        b, _ := json.Marshal(payload)        body := bytes.NewBuffer(b)        resp, err := http.Post(\"http:\/\/localhost:8080\/v1\/campaign\/click\", \"application\/json\", body)        if err != nil {            log.Println(\"post error:\", err)            continue        }        bodyBytes, _ := io.ReadAll(resp.Body)        log.Println(\"click:\", string(bodyBytes))        _ = resp.Body.Close()    }}()<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412\u043e\u0442 \u0442\u0443\u0442 \u0431\u0443\u043a\u0435\u0442 \u043f\u0440\u043e\u0431\u043b\u0435\u043c:<\/p>\n<p><strong>A. Random UUIDs \u2014 \u0434\u0435\u0434\u0443\u043f \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u0442\u0440\u0438\u0433\u0433\u0435\u0440\u0438\u0442\u0441\u044f.<\/strong> \u041f\u0430\u0440\u0430 <code>(user, campaign)<\/code> \u0432\u0441\u0435\u0433\u0434\u0430 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u0430\u044f. \u0422\u043e \u0435\u0441\u0442\u044c <strong>\u043f\u043e\u043b\u043e\u0432\u0438\u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u043a\u043e\u0434\u0430 \u043d\u0435 \u043f\u043e\u043a\u0440\u044b\u0442\u0430<\/strong> \u044d\u0442\u0438\u043c \u0442\u0435\u0441\u0442\u043e\u043c. \u0417\u0430\u0447\u0435\u043c \u043e\u043d \u0442\u043e\u0433\u0434\u0430?<\/p>\n<p><strong>B. <\/strong><a href=\"http:\/\/http.Post\" rel=\"noopener noreferrer nofollow\"><code><strong>http.Post<\/strong><\/code><\/a><strong> \u0431\u0435\u0437 \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u0430.<\/strong> <code>http.DefaultClient<\/code> \u043d\u0435 \u0438\u043c\u0435\u0435\u0442 timeout \u2014 connection \u043c\u043e\u0436\u0435\u0442 \u0432\u0438\u0441\u0435\u0442\u044c \u0434\u043e \u043f\u043e\u0431\u0435\u0434\u043d\u043e\u0433\u043e. \u041f\u043e\u0434 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439 first thing to break.<\/p>\n<p><strong>C. \u0418\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043e\u0448\u0438\u0431\u043e\u043a<\/strong> \u2014 <code>b, <em> := json.Marshal(...)<\/em><\/code><em> \u0438 <\/em><code><em>bodyBytes, <\/em> := io.ReadAll(...)<\/code>. \u041a\u0441\u0442\u0430\u0442\u0438, \u0432 Go 1.26 <code>io.ReadAll<\/code> \u0441\u0442\u0430\u043b \u0432 2 \u0440\u0430\u0437\u0430 \u0431\u044b\u0441\u0442\u0440\u0435\u0435 \u0438 \u043a\u0443\u0448\u0430\u0435\u0442 \u043d\u0430 50% \u043c\u0435\u043d\u044c\u0448\u0435 \u043f\u0430\u043c\u044f\u0442\u0438 \u2014 \u043f\u0440\u0438\u044f\u0442\u043d\u044b\u0439 \u0431\u043e\u043d\u0443\u0441, \u043d\u043e \u043d\u0435 \u043e\u0442\u043c\u0435\u043d\u044f\u0435\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0443.<\/p>\n<p><strong>D. Race \u043d\u0430 \u0441\u0442\u0430\u0440\u0442\u0435.<\/strong> \u0413\u043e\u0440\u0443\u0442\u0438\u043d\u0430 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f <strong>\u0434\u043e<\/strong> <a href=\"http:\/\/r.Run\" rel=\"noopener noreferrer nofollow\"><code>r.Run<\/code><\/a><code>()<\/code>. \u0415\u0441\u043b\u0438 \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a \u0440\u0435\u0448\u0438\u0442 \u0442\u0430\u043a \u2014 \u043f\u0435\u0440\u0432\u044b\u0439 POST \u0441\u0442\u0440\u0435\u043b\u044c\u043d\u0451\u0442 \u0440\u0430\u043d\u044c\u0448\u0435, \u0447\u0435\u043c \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0430\u0447\u0430\u043b \u0441\u043b\u0443\u0448\u0430\u0442\u044c \u043f\u043e\u0440\u0442. \u0412 \u043b\u043e\u0433\u0430\u0445 \u0443\u0432\u0438\u0434\u0438\u0442\u0435 <code>connection refused<\/code>.<\/p>\n<p><strong>E. \u0422\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u043a\u043e\u0434 \u0432 main.<\/strong> \u042d\u0442\u043e \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435 \u043c\u0435\u0441\u0442\u043e \u0434\u043b\u044f smoke-\u0442\u0435\u0441\u0442\u0430. \u0414\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0431\u0438\u043d\u0430\u0440\u044c <code>cmd\/load-tester\/<\/code> \u0438\u043b\u0438 \u0432\u043d\u0435\u0448\u043d\u0438\u0439 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 (<code>vegeta<\/code>, <code>k6<\/code>, <code>hey<\/code>).<\/p>\n<p>\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e (\u0435\u0441\u043b\u0438 \u0443\u0436 \u043d\u0443\u0436\u043d\u043e \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432 main):<\/p>\n<pre><code class=\"go\">testUsers := []string{uuid.NewString(), uuid.NewString(), uuid.NewString()}testCampaigns := []string{uuid.NewString(), uuid.NewString()}httpClient := &amp;http.Client{    Timeout: 5 * time.Second,}go func() {    \/\/ \u0416\u0434\u0451\u043c, \u043f\u043e\u043a\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u043f\u043e\u0434\u043d\u0438\u043c\u0435\u0442\u0441\u044f    time.Sleep(500 * time.Millisecond)        ticker := time.NewTicker(1 * time.Second)    defer ticker.Stop()        i := 0    for range ticker.C {        payload := struct{ \/* ... *\/ }{            UserUUID:     testUsers[i%len(testUsers)],         \/\/ \u043f\u043e\u0432\u0442\u043e\u0440\u044b \u2192 \u0434\u0435\u0434\u0443\u043f \u0441\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442!            CampaignUUID: testCampaigns[i%len(testCampaigns)],            Country:      \"US\",        }        b, err := json.Marshal(payload)        if err != nil {            slog.Error(\"marshal\", \"err\", err)            continue        }                resp, err := httpClient.Post(            \"http:\/\/localhost:8080\/v1\/campaign\/click\",            \"application\/json\",            bytes.NewBuffer(b),        )        if err != nil {            slog.Error(\"post\", \"err\", err)            continue        }                bodyBytes, err := io.ReadAll(resp.Body)        resp.Body.Close()        if err != nil {            slog.Error(\"read body\", \"err\", err)            continue        }        slog.Info(\"click\", \"response\", string(bodyBytes))        i++    }}()<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041b\u0443\u0447\u0448\u0435 \u0432\u043e\u043e\u0431\u0449\u0435 \u0432\u044b\u043a\u0438\u043d\u0443\u0442\u044c \u044d\u0442\u0443 \u0433\u043e\u0440\u0443\u0442\u0438\u043d\u0443 \u0438\u0437 main \u0438 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u044c \u0432 <code>cmd\/load-tester\/main.go<\/code> \u0438\u043b\u0438 \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0430 <code>k6<\/code> script.<\/p>\n<p><strong>\u041c\u043e\u0440\u0430\u043b\u044c:<\/strong> \u0415\u0441\u043b\u0438 \u0442\u044b \u043f\u0438\u0448\u0435\u0448\u044c \u201c\u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u043a\u043e\u0434\u201d \u2014 \u0443\u0431\u0435\u0434\u0438\u0441\u044c, \u0447\u0442\u043e \u043e\u043d \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u0442 \u0442\u043e, \u0447\u0442\u043e \u043d\u0443\u0436\u043d\u043e. Random data \u0447\u0430\u0441\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0435\u043c\u043e\u0441\u0442\u0438 \u0438 \u043f\u0440\u043e\u043f\u0443\u0441\u043a \u043a\u0435\u0439\u0441\u043e\u0432 \u0441 \u043f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u044f\u043c\u0438. \u0418 \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439 <code>http.Post<\/code>\/<code>http.Get<\/code> \u0431\u0435\u0437 \u044f\u0432\u043d\u043e\u0433\u043e \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0441 timeout \u2014 \u044d\u0442\u043e \u043f\u0435\u0440\u0432\u043e\u0435, \u0447\u0442\u043e \u0443\u043f\u0430\u0434\u0451\u0442 \u043f\u043e\u0434 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439.<\/p>\n<h3>\u0418\u0442\u043e\u0433\u043e \u0427\u0430\u0441\u0442\u044c 2: 13 \u043f\u0440\u043e\u0431\u043b\u0435\u043c API &amp; Reliability<\/h3>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">#<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u0422\u0438\u043f<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">9<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>error<\/code> \u043d\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0432 return<\/p>\n<\/td>\n<td>\n<p align=\"left\">API convention<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">10<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>Save() error<\/code> \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f<\/p>\n<\/td>\n<td>\n<p align=\"left\">Error handling<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">11<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>_ = ctx.ShouldBindJSON()<\/code> \u0431\u0435\u0437 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438<\/p>\n<\/td>\n<td>\n<p align=\"left\">Input validation<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">12<\/p>\n<\/td>\n<td>\n<p align=\"left\">Country \u0431\u0435\u0437 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043d\u0430 ISO 3166-1<\/p>\n<\/td>\n<td>\n<p align=\"left\">Input validation<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">13<\/p>\n<\/td>\n<td>\n<p align=\"left\">URL \u0441 \u0434\u0432\u0443\u043c\u044f <code>?<\/code> \u0432\u043c\u0435\u0441\u0442\u043e <code>?<\/code> + <code>&amp;<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">Business bug<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">14<\/p>\n<\/td>\n<td>\n<p align=\"left\">URL \u0431\u0435\u0437 <code>url.QueryEscape<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">Security\/correctness<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">15<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0412\u0441\u0435\u0433\u0434\u0430 <code>200 OK<\/code> \u2014 \u043d\u0435\u0442 <code>201 Created<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">API design<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">16<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041d\u0435\u0442 <code>context.Context<\/code> \u0432 repository<\/p>\n<\/td>\n<td>\n<p align=\"left\">Cancellation<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">17<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041d\u0435\u0442 graceful shutdown<\/p>\n<\/td>\n<td>\n<p align=\"left\">Reliability<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">18<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041d\u0435\u0442 HTTP timeout\u2019\u043e\u0432 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435<\/p>\n<\/td>\n<td>\n<p align=\"left\">Slowloris<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">19<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>log.Println<\/code> \u0432\u043c\u0435\u0441\u0442\u043e <code>slog<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">Observability<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">20<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0422\u0435\u0441\u0442 \u0441 random UUIDs + <a href=\"http:\/\/http.Post\" rel=\"noopener noreferrer nofollow\">http.Post<\/a> \u0431\u0435\u0437 timeout + ignored errors + race<\/p>\n<\/td>\n<td>\n<p align=\"left\">Test quality<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>\u0418\u0437 13 \u0431\u0430\u0433\u043e\u0432 \u0432 \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043d\u0430 \u0438\u043d\u0442\u0435\u0440\u0432\u044c\u044e \u044f \u043d\u0430\u0448\u0451\u043b <strong>11<\/strong>. \u041a\u043e\u0433\u0434\u0430 \u043f\u0438\u0441\u0430\u043b \u043f\u043e\u0441\u0442 &#8212; \u0443\u0432\u0438\u0434\u0435\u043b \u0447\u0442\u043e \u043e\u0434\u0438\u043d \u0438\u0437 \u0431\u0430\u0433\u043e\u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u044f \u043d\u0430\u0448\u0435\u043b \u043f\u0435\u0440\u0432\u043e\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e &#8212; \u0431\u0430\u0433\u043e\u043c \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f. \u0422\u0430\u043a \u0447\u0442\u043e \u0431\u0430\u0433\u043e\u0432 \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043c\u0435\u043d\u044c\u0448\u0435 \u0447\u0435\u043c \u043f\u0438\u0441\u0430\u043b \u0432 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438. \u041f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u043b  <strong>#19<\/strong> (<code>log.Println<\/code> \u0432\u043c\u0435\u0441\u0442\u043e <code>slog<\/code> \u2014 \u0433\u043b\u0430\u0437 \u043d\u0435 \u0437\u0430\u0446\u0435\u043f\u0438\u043b\u0441\u044f, \u0431\u044b\u043b \u0441\u0444\u043e\u043a\u0443\u0441\u0438\u0440\u043e\u0432\u0430\u043d \u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0445 \u0431\u0430\u0433\u0430\u0445, \u043d\u0435 \u043d\u0430 code style). \u0415\u0441\u043b\u0438 \u0441\u0443\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u044c\u044e: <strong>18 \u0438\u0437 20 \u0437\u0430 30 \u043c\u0438\u043d\u0443\u0442<\/strong>. \u0422\u0440\u0438 \u043f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0445 \u2014 TOCTOU race, compile error \u0438 slog \u2014 \u043a\u0430\u0436\u0434\u044b\u0439 \u043f\u043e \u0441\u0432\u043e\u0435\u043c\u0443 \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u043d\u044b\u0439, \u0438 \u043a\u0430\u0436\u0434\u044b\u0439 \u043d\u0430\u0443\u0447\u0438\u043b \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u043c\u0443 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u0443 \u201c\u043a\u0443\u0434\u0430 \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0440\u0430\u0437\u201d.<\/p>\n<h4>\u0427\u0442\u043e \u043d\u043e\u0432\u043e\u0433\u043e \u0432 Go 1.26 \u0434\u043b\u044f \u0431\u043e\u0440\u044c\u0431\u044b \u0441 \u044d\u0442\u0438\u043c\u0438 \u0431\u0430\u0433\u0430\u043c\u0438<\/h4>\n<ul>\n<li>\n<p><code><strong>errors.AsType[T]<\/strong><\/code> \u2014 \u0442\u0438\u043f\u043e\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 generic-\u0432\u0430\u0440\u0438\u0430\u043d\u0442 <a href=\"http:\/\/errors.As\" rel=\"noopener noreferrer nofollow\"><code>errors.As<\/code><\/a>. \u0427\u0438\u0449\u0435 \u043f\u0430\u0440\u0441\u044f\u0442\u0441\u044f \u0434\u043e\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u0448\u0438\u0431\u043a\u0438 (\u0441\u043c. \u0431\u0430\u0433 #11).<\/p>\n<\/li>\n<li>\n<p><code><strong>io.ReadAll<\/strong><\/code><strong> \u00d72 \u0431\u044b\u0441\u0442\u0440\u0435\u0435, \u221250% \u043f\u0430\u043c\u044f\u0442\u0438<\/strong> \u2014 \u0442\u0435, \u043a\u0442\u043e \u0447\u0438\u0442\u0430\u0435\u0442 HTTP body, \u043f\u043e\u043b\u0443\u0447\u0430\u044e\u0442 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u044b\u0439 \u0431\u0443\u0441\u0442 (\u0441\u043c. \u0431\u0430\u0433 #21).<\/p>\n<\/li>\n<li>\n<p><strong>HTTP\/2 <\/strong><code><strong>StrictMaxConcurrentRequests<\/strong><\/code> \u2014 \u0442\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0438\u0432\u0430\u0442\u044c concurrent requests \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u0430.<\/p>\n<\/li>\n<li>\n<p><strong>HTTP 307 \u0434\u043b\u044f trailing slash redirects<\/strong> (\u0432\u043c\u0435\u0441\u0442\u043e 301) \u2014 breaking change, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0440\u0435\u0434\u0438\u0440\u0435\u043a\u0442\u044b \u0432 API.<\/p>\n<\/li>\n<li>\n<p><code><strong>net\/url.Parse<\/strong><\/code><strong> \u0441\u0442\u0430\u043b \u0441\u0442\u0440\u043e\u0436\u0435<\/strong> \u043a malformed URL\u2019\u0430\u043c \u0441 \u0434\u0432\u043e\u0435\u0442\u043e\u0447\u0438\u044f\u043c\u0438 \u0432 host. \u0415\u0441\u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u0441\u043b\u043e\u043c\u0430\u043b\u043e\u0441\u044c \u2014 <code>GODEBUG=urlstrictcolons=0<\/code> \u0432\u0435\u0440\u043d\u0451\u0442 \u0441\u0442\u0430\u0440\u043e\u0435 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435.<\/p>\n<\/li>\n<\/ul>\n<h3>\u0424\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0439 \u0438\u0442\u043e\u0433: 20 \u0431\u0430\u0433\u043e\u0432 \u043d\u0430 150 \u0441\u0442\u0440\u043e\u043a<\/h3>\n<p>\u041f\u043e\u043b\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0437 \u043e\u0431\u0435\u0438\u0445 \u0447\u0430\u0441\u0442\u0435\u0439:<\/p>\n<p><strong>Concurrency &amp; Memory (\u0427\u0430\u0441\u0442\u044c 1):<\/strong><\/p>\n<ol>\n<li>\n<p>\u0417\u0430\u0431\u044b\u0442\u044b\u0439 <code>Lock<\/code> \u0432 Save (data race)<\/p>\n<\/li>\n<li>\n<p>\u041a\u0430\u043d\u0430\u043b \u0431\u0435\u0437 consumer\u2019\u0430 (goroutine leak)<\/p>\n<\/li>\n<li>\n<p>Publisher \u043f\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044e (channel duplication)<\/p>\n<\/li>\n<li>\n<p>PurgeOld \u043d\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f (memory leak)<\/p>\n<\/li>\n<li>\n<p>PurgeOld O(N\u00b2) \u043f\u043e\u0434 Lock (latency spike)<\/p>\n<\/li>\n<li>\n<p>TOCTOU race \u0432 \u0434\u0435\u0434\u0443\u043f\u043b\u0438\u043a\u0430\u0446\u0438\u0438<\/p>\n<\/li>\n<li>\n<p>O(N) \u043f\u043e\u0438\u0441\u043a \u0432 \u0433\u043e\u0440\u044f\u0447\u0435\u043c \u043f\u0443\u0442\u0438<\/p>\n<\/li>\n<li>\n<p><code>go publisher.Publish()<\/code> \u0431\u0435\u0437 context<\/p>\n<p><strong>API &amp; Reliability (\u0427\u0430\u0441\u0442\u044c 2):<\/strong><\/p>\n<\/li>\n<li>\n<p><code>error<\/code> \u043d\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0432 return<\/p>\n<\/li>\n<li>\n<p><code>Save() error<\/code> \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f<\/p>\n<\/li>\n<li>\n<p>\u041d\u0435\u0442 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u0432\u0445\u043e\u0434\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445<\/p>\n<\/li>\n<li>\n<p>Country \u043d\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u043d\u0430 ISO 3166-1<\/p>\n<\/li>\n<li>\n<p>\u0411\u0438\u0442\u044b\u0439 URL (\u0434\u0432\u0430 <code>?<\/code>)<\/p>\n<\/li>\n<li>\n<p>URL \u0431\u0435\u0437 escaping<\/p>\n<\/li>\n<li>\n<p>Status 200 \u0432\u043c\u0435\u0441\u0442\u043e 201<\/p>\n<\/li>\n<li>\n<p>\u041d\u0435\u0442 <code>context.Context<\/code><\/p>\n<\/li>\n<li>\n<p>\u041d\u0435\u0442 graceful shutdown<\/p>\n<\/li>\n<li>\n<p>\u041d\u0435\u0442 HTTP timeout\u2019\u043e\u0432 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435<\/p>\n<\/li>\n<li>\n<p><code>log.Println<\/code> \u0432\u043c\u0435\u0441\u0442\u043e <code>slog<\/code><\/p>\n<\/li>\n<li>\n<p>\u0422\u0435\u0441\u0442\u043e\u0432\u0430\u044f \u0433\u043e\u0440\u0443\u0442\u0438\u043d\u0430 \u0431\u0435\u0441\u043f\u043e\u043b\u0435\u0437\u043d\u0430 (random UUIDs + <a href=\"http:\/\/http.Post\" rel=\"noopener noreferrer nofollow\">http.Post<\/a> \u0431\u0435\u0437 timeout)<\/p>\n<\/li>\n<\/ol>\n<h3>\u0427\u0442\u043e \u044f \u043f\u043e\u043d\u044f\u043b \u043f\u0440\u043e code review \u0438\u043d\u0442\u0435\u0440\u0432\u044c\u044e<\/h3>\n<p><strong>\u0420\u0430\u0437.<\/strong> Code review \u2014 \u044d\u0442\u043e <strong>\u043d\u0435<\/strong> \u201c\u043d\u0430\u0439\u0434\u0438 \u043e\u043f\u0435\u0447\u0430\u0442\u043a\u0443\u201d. \u042d\u0442\u043e \u201c\u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u044c, \u0447\u0442\u043e \u044d\u0442\u043e \u0442\u0432\u043e\u0439 \u043a\u043e\u0434 \u0432 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0435 \u043d\u0430 1000 RPS, \u0447\u0442\u043e \u0441\u043b\u043e\u043c\u0430\u0435\u0442\u0441\u044f?\u201d. \u0414\u0443\u043c\u0430\u0439 \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u201c\u043a\u0430\u043a \u044f \u0431\u0443\u0434\u0443 \u044d\u0442\u043e \u0434\u0435\u0431\u0430\u0436\u0438\u0442\u044c \u0432 \u0441\u0443\u0431\u0431\u043e\u0442\u0443 \u043d\u043e\u0447\u044c\u044e\u201d.<\/p>\n<p><strong>\u0414\u0432\u0430.<\/strong> \u0418\u0434\u0442\u0438 <strong>\u0441\u0432\u0435\u0440\u0445\u0443 \u0432\u043d\u0438\u0437<\/strong> \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442. \u041d\u0430\u0448\u0451\u043b \u043f\u0435\u0440\u0432\u044b\u0439 \u0431\u0430\u0433 \u2014 \u0432\u044b\u043f\u0438\u0441\u0430\u043b, \u0438\u0434\u0451\u0448\u044c \u0434\u0430\u043b\u044c\u0448\u0435. \u0418\u043d\u0430\u0447\u0435 \u0437\u0430\u043b\u0438\u043f\u043d\u0435\u0448\u044c \u043d\u0430 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0435 \u0438 \u043f\u043e\u0442\u0435\u0440\u044f\u0435\u0448\u044c \u0432\u0440\u0435\u043c\u044f.<\/p>\n<p><strong>\u0422\u0440\u0438.<\/strong> \u041a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u0438 \u043e\u0448\u0438\u0431\u043e\u043a, \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u0441\u0442\u043e\u0438\u0442 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u044f\u0432\u043d\u043e:<\/p>\n<ul>\n<li>\n<p><strong>Compilation<\/strong>: package, \u0438\u043c\u043f\u043e\u0440\u0442\u044b, \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u0438\u044f (\u044d\u0442\u043e \u0447\u0430\u0441\u0442\u043e \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u044e\u0442, \u0438 \u0437\u0440\u044f)<\/p>\n<\/li>\n<li>\n<p><strong>Concurrency<\/strong>: \u0433\u0434\u0435 Lock? \u0433\u0434\u0435 race? \u0433\u0434\u0435 \u0437\u0430\u0431\u044b\u0442\u044b\u0439 channel close?<\/p>\n<\/li>\n<li>\n<p><strong>Resource leaks<\/strong>: \u0433\u043e\u0440\u0443\u0442\u0438\u043d\u044b, \u043a\u0430\u043d\u0430\u043b\u044b, \u0444\u0430\u0439\u043b\u044b, connections, \u043f\u0430\u043c\u044f\u0442\u044c<\/p>\n<\/li>\n<li>\n<p><strong>Error handling<\/strong>: \u0433\u0434\u0435 \u043f\u0440\u043e\u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u044b errors? \u0433\u0434\u0435 <code>nil<\/code> panic\u2019\u043d\u0435\u0442?<\/p>\n<\/li>\n<li>\n<p><strong>Boundaries<\/strong>: \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u043d\u0430 input, escaping \u043d\u0430 output<\/p>\n<\/li>\n<li>\n<p><strong>API design<\/strong>: status codes, error returns, context propagation<\/p>\n<\/li>\n<li>\n<p><strong>Reliability<\/strong>: shutdown, timeouts, retries, observability<\/p>\n<\/li>\n<li>\n<p><strong>Performance hot paths<\/strong>: O(N) \u0432 handler \u2014 \u044d\u0442\u043e \u043f\u043b\u043e\u0445\u043e<\/p>\n<\/li>\n<\/ul>\n<p><strong>\u0427\u0435\u0442\u044b\u0440\u0435.<\/strong> <strong>\u041e\u0437\u0432\u0443\u0447\u0438\u0432\u0430\u0439 \u043c\u044b\u0441\u043b\u0438 \u0432\u0441\u043b\u0443\u0445<\/strong>. \u042d\u0442\u043e \u0441\u0438\u043b\u044c\u043d\u043e \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442: \u0438\u043d\u0442\u0435\u0440\u0432\u044c\u044e\u0435\u0440\u0443 \u0432\u0430\u0436\u043d\u043e \u043f\u043e\u043d\u044f\u0442\u044c, <em>\u043a\u0430\u043a<\/em> \u0442\u044b \u0434\u0443\u043c\u0430\u0435\u0448\u044c, \u0430 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e <em>\u0447\u0442\u043e<\/em> \u043d\u0430\u0448\u0451\u043b. \u042f \u043d\u0430\u0447\u0430\u043b \u0441 \u201c\u0442\u0430\u043a, \u0443 \u043d\u0430\u0441 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0441 RWMutex, \u043f\u0440\u043e\u0432\u0435\u0440\u044e, \u0432\u0441\u0435 \u043b\u0438 write-\u043c\u0435\u0442\u043e\u0434\u044b \u0435\u0433\u043e \u0431\u0435\u0440\u0443\u0442\u201d \u2014 \u0438 \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0439\u043c\u0430\u043b \u0431\u0430\u0433 #1. \u0414\u0430\u043b\u044c\u0448\u0435 \u0448\u0451\u043b \u043f\u043e \u0447\u0435\u043a\u043b\u0438\u0441\u0442\u0443 \u0432\u0441\u043b\u0443\u0445: \u201c\u0442\u0435\u043f\u0435\u0440\u044c \u043a\u0430\u043d\u0430\u043b \u2014 \u043a\u0442\u043e consumer? \u043d\u0435\u0442 consumer\u2019\u0430 \u2014 \u0431\u0430\u0433 #2\u201d, \u0438 \u0442\u0430\u043a \u0434\u0430\u043b\u0435\u0435. \u041c\u0438\u043d\u0443\u0441\u043e\u043c \u2014 \u043d\u0430 \u0447\u0435\u043a\u043b\u0438\u0441\u0442\u0435 \u043b\u0435\u0433\u043a\u043e \u043f\u0440\u043e\u0432\u0430\u043b\u0438\u0442\u044c \u0432\u0435\u0449\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432 \u0447\u0435\u043a\u043b\u0438\u0441\u0442 \u043d\u0435 \u043f\u043e\u043f\u0430\u043b\u0438 (\u043a\u0430\u043a \u0443 \u043c\u0435\u043d\u044f \u0441 TOCTOU \u0438 slog).<\/p>\n<p><strong>\u041f\u044f\u0442\u044c.<\/strong> \u0415\u0441\u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u043d\u0435\u043f\u043e\u043d\u044f\u0442\u043d\u043e \u2014 <strong>\u0441\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0439<\/strong>. \u201c\u0410 \u044d\u0442\u043e\u0442 \u0441\u0435\u0440\u0432\u0438\u0441 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0434 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439?\u201d, \u201c\u0410 Publisher \u043a\u0443\u0434\u0430 \u043f\u0438\u0448\u0435\u0442 \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438?\u201d, \u201c\u041d\u0443\u0436\u043d\u0430 \u043b\u0438 exactly-once \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u044f?\u201d. \u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u043c\u0435\u043d\u044f\u0435\u0442 \u043f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442\u044b \u0431\u0430\u0433\u043e\u0432.<\/p>\n<h3>\u0427\u0442\u043e \u0434\u0430\u043b\u044c\u0448\u0435: Telegram + \u043a\u0443\u0440\u0441<\/h3>\n<p>\u0412\u0441\u0435 \u044d\u0442\u0438 \u0440\u0430\u0437\u0431\u043e\u0440\u044b \u2014 \u0447\u0430\u0441\u0442\u044c \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0438 \u043a \u0441\u0435\u0440\u0438\u0438 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u043e\u0432 \u043f\u0440\u043e <strong>\u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0439 Senior Go<\/strong>. \u041d\u0435 \u0442\u0435\u043e\u0440\u0438\u044f \u0438\u0437 \u043a\u043d\u0438\u0436\u0435\u043a, \u0430 \u0432\u043e\u0442 \u0442\u0430\u043a\u0438\u0435 \u043a\u0443\u0441\u043a\u0438 \u043a\u043e\u0434\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u0434\u0430\u044e\u0442 \u043d\u0430 \u0438\u043d\u0442\u0435\u0440\u0432\u044c\u044e \u0442\u043e\u043f-\u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438.<\/p>\n<p>\u0412 <strong>Telegram-\u043a\u0430\u043d\u0430\u043b\u0435 <\/strong><a href=\"https:\/\/t.me\/go_interview_prep_ru\" rel=\"noopener noreferrer nofollow\"><strong>@go_interview_prep_ru<\/strong><\/a> \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u043e \u0432\u044b\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u044e:<\/p>\n<p>\ud83d\udcdd <strong>Code review challenges<\/strong> \u2014 \u043a\u0443\u0441\u043e\u043a \u043a\u043e\u0434\u0430, \u043d\u0430\u0439\u0434\u0438 \u0431\u0430\u0433\u0438 (\u0441 \u0440\u0430\u0437\u0431\u043e\u0440\u0430\u043c\u0438 \u0447\u0435\u0440\u0435\u0437 2 \u0434\u043d\u044f)<br \/> \ud83e\udde0 <strong>Go quiz<\/strong> \u043f\u043e 1 \u0432\u043e\u043f\u0440\u043e\u0441\u0443 \u0432 \u0434\u0435\u043d\u044c \u2014 \u0442\u043e, \u0447\u0442\u043e \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u0441\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u044e\u0442<br \/> \ud83d\udca1 <strong>Patterns &amp; anti-patterns<\/strong> \u0438\u0437 production<br \/> \ud83d\udd27 <strong>\u0413\u043e\u0442\u043e\u0432\u044b\u0435 snippet\u2019\u044b<\/strong> \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0440\u0432\u044c\u044e (worker pool, rate limiter, \u0438 \u0442.\u0434.)<\/p>\n<p>\u0422\u0430\u043a \u0436\u0435 \u0433\u043e\u0442\u043e\u0432\u043b\u044e <strong> \u043a\u0443\u0440\u0441<\/strong> \u043d\u0430 Stepik \u2014 <a href=\"https:\/\/stepik.org\/course\/286079\/promo\" rel=\"noopener noreferrer nofollow\">Go Senior Interview<\/a>. \u0412 \u043d\u0435\u043c \u0445\u043e\u0447\u0443 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u043e\u043c\u0435\u043d\u0442\u043e\u0432:<\/p>\n<ul>\n<li>\n<p>GMP Scheduler (\u0433\u043e\u0442\u043e\u0432!)<\/p>\n<\/li>\n<li>\n<p>Memory Model &amp; Escape Analysis (\u0433\u043e\u0442\u043e\u0432!)<\/p>\n<\/li>\n<li>\n<p>Garbage Collector Internals<\/p>\n<\/li>\n<li>\n<p>Interfaces &amp; Reflection<\/p>\n<\/li>\n<li>\n<p>Generics in Production<\/p>\n<\/li>\n<li>\n<p>Context &amp; Error Patterns<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u0432\u0430 \u043c\u043e\u0434\u0443\u043b\u044f \u0443\u0436\u0435 \u0441\u0434\u0435\u043b\u0430\u043b. \u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0433\u043e\u0442\u043e\u0432\u043b\u044e. \u041a\u0443\u0440\u0441 \u043a\u0430\u043a \u0438 \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c\u043c \u043a\u0430\u043d\u0430\u043b \u0411\u0415\u0421\u041f\u041b\u0410\u0422\u041d\u042b\u0419 &#8212; \u0435\u0441\u043b\u0438 \u043a\u0442\u043e \u043e\u043f\u044f\u0442\u044c \u044d\u0442\u043e\u0442 \u043f\u043e\u0441\u0442 \u0440\u0435\u0448\u0438\u0442 \u043a\u0430\u043a \u0440\u0435\u043a\u043b\u0430\u043c\u0430 \u043e\u0431\u043e\u0437\u043d\u0430\u0447\u0438\u0442\u044c. <\/p>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u044f \u0441\u0432\u043e\u0438 \u043f\u043e\u0441\u0442\u044b \u043d\u0435 \u0437\u0430\u043a\u0430\u043d\u0447\u0438\u0432\u0430\u044e, \u0431\u0443\u0434\u0443 \u0434\u0430\u043b\u044c\u0448\u0435 \u0432\u044b\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0435 \u043c\u043e\u043c\u0435\u043d\u0442\u044b \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u043b \u043d\u0430 \u0441\u043e\u0431\u0435\u0441\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u0438. \u0418 \u043a\u0430\u043a \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043b \u0432 \u043e\u0434\u043d\u043e\u0439 \u0441\u043e\u0446\u0441\u0435\u0442\u0438 &#8212; \u0432\u0441\u0435\u043c \u0445\u043e\u0440\u043e\u0448\u0438\u0445 \u043e\u0444\u0444\u0435\u0440\u043e\u0432 \u0438 \u0442\u043e\u043b\u043a\u043e\u0432\u044b\u0445 \u0438\u043d\u0442\u0435\u0440\u0432\u044c\u044e\u0435\u0440\u043e\u0432, \u0430 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044f\u043c &#8212; \u043a\u0432\u0430\u043b\u0438\u0444\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u043e\u0432.<\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1033634\/\">https:\/\/habr.com\/ru\/articles\/1033634\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u041f\u0440\u043e\u0434\u043e\u043b\u0436\u0435\u043d\u0438\u0435 \u0440\u0430\u0437\u0431\u043e\u0440\u0430 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430 \u0441 \u0441\u043e\u0431\u0435\u0441\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u044f. \u0412 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043b\u0438 8 \u043f\u0440\u043e\u0431\u043b\u0435\u043c concurrency \u0438 memory: race conditions, \u0443\u0442\u0435\u0447\u043a\u0438 \u0433\u043e\u0440\u0443\u0442\u0438\u043d, \u043f\u0440\u043e\u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 mutex, TOCTOU. \u042d\u0442\u043e \u0431\u044b\u043b\u0430 \u043f\u0435\u0440\u0432\u0430\u044f \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u0430 \u0438\u0437 21 \u0431\u0430\u0433\u0430 \u0432 \u043e\u0434\u043d\u043e\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u0435 \u043d\u0430 150 \u0441\u0442\u0440\u043e\u043a.\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u2014 \u0432\u0442\u043e\u0440\u0430\u044f \u0447\u0430\u0441\u0442\u044c. \u0422\u0443\u0442 \u043d\u0435\u0442 \u0441\u0442\u0440\u0430\u0448\u043d\u044b\u0445 race conditions, \u043d\u043e \u0435\u0441\u0442\u044c \u0442\u043e, \u0447\u0442\u043e \u0432\u044b\u0434\u0430\u0451\u0442 \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u043d\u0430 \u0441\u043e\u0431\u0435\u0441\u0435: \u043e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u0435 \u043a \u043e\u0448\u0438\u0431\u043a\u0430\u043c, \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f, API design, graceful shutdown, observability. \u042d\u0442\u0438 \u0431\u0430\u0433\u0438 \u043d\u0435 \u0443\u043f\u0430\u0434\u0443\u0442 \u201c\u0432\u0434\u0440\u0443\u0433\u201d \u0432 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0435 \u2014 \u043e\u043d\u0438 \u0431\u0443\u0434\u0443\u0442 \u0442\u0438\u0445\u043e \u043f\u0438\u043b\u0438\u0442\u044c \u0432\u0430\u043c \u043a\u043e\u0441\u0442\u044b\u043b\u044c \u0437\u0430 \u043a\u043e\u0441\u0442\u044b\u043b\u0451\u043c, \u043f\u043e\u043a\u0430 \u043a\u0442\u043e-\u0442\u043e \u043d\u0435 \u0441\u044f\u0434\u0435\u0442 \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c. \u0410\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e \u0434\u043b\u044f Go 1.26.\u041d\u0430\u043f\u043e\u043c\u043d\u044e \u0438\u0442\u043e\u0433 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438: \u0438\u0437 8 \u0431\u0430\u0433\u043e\u0432 \u043f\u0440\u043e concurrency \u043d\u0430 \u0438\u043d\u0442\u0435\u0440\u0432\u044c\u044e \u043d\u0430\u0448\u0451\u043b 7, \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u043b \u0442\u043e\u043b\u044c\u043a\u043e TOCTOU race. \u0412 \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0438\u0437 13 \u0431\u0430\u0433\u043e\u0432 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u043b \u0434\u0432\u0430: package applike \u0441 func main() (\u0442\u043e, \u0447\u0442\u043e \u043a\u043e\u0434 \u043d\u0435 \u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u2014 \u0431\u0430\u043d\u0430\u043b\u044c\u043d\u043e \u043d\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u043b \u043d\u0430 \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0430\u043a\u0435\u0442\u0430) \u0438 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 slog (\u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0435 \u0437\u0430\u0446\u0435\u043f\u0438\u043b\u0441\u044f \u0437\u0430 log.Println, \u0430 \u0437\u0440\u044f). \u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 11 \u2014 \u043f\u043e\u0439\u043c\u0430\u043b. \u0420\u0430\u0441\u0441\u043a\u0430\u0436\u0443, \u043a\u0430\u043a\u0438\u043c\u0438 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u0430\u043c\u0438 \u0432 \u0447\u0442\u0435\u043d\u0438\u0438 \u043a\u043e\u0434\u0430 \u044f \u0438\u0445 \u0432\u044b\u043b\u0430\u0432\u043b\u0438\u0432\u0430\u043b.\u041d\u0430\u043f\u043e\u043c\u043d\u044e, \u0447\u0442\u043e \u0437\u0430 \u043a\u043e\u0434\u041c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441 \u0442\u0440\u0435\u043a\u0438\u043d\u0433\u0430 \u0440\u0435\u043a\u043b\u0430\u043c\u043d\u044b\u0445 \u043a\u043b\u0438\u043a\u043e\u0432 \u043d\u0430 Gin \u0441 in-memory storage. \u041f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 POST \u0441 (user_uuid, campaign_uuid, country), \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 tracking link, \u0434\u0435\u0434\u0443\u043f\u043b\u0438\u0446\u0438\u0440\u0443\u0435\u0442 \u0437\u0430 5 \u0441\u0435\u043a\u0443\u043d\u0434, \u043f\u0430\u0431\u043b\u0438\u0448\u0438\u0442 \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c.\u0412 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043c\u044b \u043f\u043e\u0447\u0438\u043d\u0438\u043b\u0438 mutex\u2019\u044b, \u0443\u0442\u0435\u0447\u043a\u0438 \u0438 \u043a\u0430\u043d\u0430\u043b\u044b. \u0422\u0435\u043f\u0435\u0440\u044c \u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0430 \u0432\u0441\u0451 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435.10. \u201c\u0410 return values \u0443 \u043d\u0430\u0441 \u043a\u0430\u043a, \u043f\u043e \u0444\u044d\u043d-\u0448\u0443\u044e?\u201d\u0421\u043c\u043e\u0442\u0440\u044e \u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0443 GetRecent:func (r *InMemoryClickRepository) GetRecent(    userUUID, campaignUUID string,     maxAge time.Duration,) (error, *CampaignClick, bool) {    \/\/ &#8230;}\u041d\u0430 \u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0435 \u0433\u043b\u0430\u0437 \u0441\u0430\u043c \u0437\u0430\u0446\u0435\u043f\u0438\u043b\u0441\u044f \u2014 \u043e\u043d\u0430 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f \u043a\u0440\u0438\u0432\u043e, \u043a\u0430\u043a \u0438\u043d\u043e\u0441\u0442\u0440\u0430\u043d\u043d\u044b\u0439 \u0430\u043a\u0446\u0435\u043d\u0442 \u0432 Go-\u043a\u043e\u0434\u0435. \u041e\u0437\u0432\u0443\u0447\u0438\u0432\u0430\u044e: \u201c\u0412 Go \u043a\u043e\u043d\u0432\u0435\u043d\u0446\u0438\u044f \u2014 error \u0432\u0441\u0435\u0433\u0434\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439. \u0422\u0443\u0442 \u043e\u043d \u043f\u0435\u0440\u0432\u044b\u0439, \u0438 \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u0435\u0433\u043e \u043d\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 (\u0432\u0435\u0437\u0434\u0435 return nil, &#8230;). \u041b\u0438\u0448\u043d\u0438\u0439 \u0441\u043b\u043e\u0442 \u0432 \u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0435 \u2014 \u0432\u044b\u043a\u0438\u043d\u0443\u0442\u044c\u201d.\u0412 Go \u044d\u0442\u043e \u043d\u0435\u0433\u043b\u0430\u0441\u043d\u0430\u044f \u043a\u043e\u043d\u0432\u0435\u043d\u0446\u0438\u044f: error \u0432\u0441\u0435\u0433\u0434\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439. \u042d\u0442\u043e \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u0442\u0438\u043b\u0435\u0432\u043e\u0435 \u2014 \u044d\u0442\u043e \u0432\u043b\u0438\u044f\u0435\u0442 \u043d\u0430 \u0447\u0438\u0442\u0430\u0435\u043c\u043e\u0441\u0442\u044c, \u043d\u0430 \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 linter\u2019\u0430\u043c\u0438 (errcheck \u043e\u0436\u0438\u0434\u0430\u0435\u0442 error \u0432 \u043a\u043e\u043d\u0446\u0435), \u043d\u0430 \u0442\u043e, \u043a\u0430\u043a \u043b\u044e\u0434\u0438 \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0435 \u0431\u0443\u0434\u0443\u0442 \u044d\u0442\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c. \u041a\u0430\u0436\u0434\u044b\u0439 Go-\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043d\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0435 \u043f\u0438\u0448\u0435\u0442:val, err := someFunc()\u0410 \u0442\u0443\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f:err, click, ok := repo.GetRecent(&#8230;)\u0418 \u0442\u044b \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u0441\u043f\u043e\u0442\u044b\u043a\u0430\u0435\u0448\u044c\u0441\u044f. \u041f\u043b\u044e\u0441 \u2014 \u0437\u0430\u0447\u0435\u043c \u0442\u0443\u0442 \u0432\u043e\u043e\u0431\u0449\u0435 error? \u0424\u0443\u043d\u043a\u0446\u0438\u044f \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u0435\u0433\u043e \u043d\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 (\u0432\u0435\u0437\u0434\u0435 return nil, &#8230;). \u041b\u0438\u0448\u043d\u0438\u0439 \u0441\u043b\u043e\u0442 \u0432 \u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0443\u0442\u0430\u0435\u0442.\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e:func (r *InMemoryClickRepository) GetRecent(    userUUID, campaignUUID string,    maxAge time.Duration,) (*CampaignClick, bool) {    \/\/ \u0435\u0441\u043b\u0438 error \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u043d\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u2014 \u0443\u0431\u0438\u0440\u0430\u0435\u043c \u0435\u0433\u043e}\u041c\u043e\u0440\u0430\u043b\u044c: \u041a\u043e\u043d\u0432\u0435\u043d\u0446\u0438\u0438 \u044f\u0437\u044b\u043a\u0430 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0442 \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u0442\u0430\u043a. error \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439, context.Context \u043f\u0435\u0440\u0432\u044b\u0439, ok bool \u0434\u043b\u044f \u0434\u0432\u043e\u0439\u043d\u043e\u0433\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430 \u0441 map\/cast. \u041d\u0430\u0440\u0443\u0448\u0430\u0435\u0448\u044c \u2014 \u0442\u0432\u043e\u0439 \u043a\u043e\u0434 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043a\u0430\u043a \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0439 \u0447\u0435\u043b\u043e\u0432\u0435\u043a\u043e\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 Go \u043d\u0435 \u0437\u043d\u0430\u0435\u0442.11. Save() \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 error \u2014 \u0430 \u043c\u044b \u0435\u0433\u043e \u0432\u044b\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c\u0421\u043c\u043e\u0442\u0440\u044e \u043d\u0430 handler:c.repo.Save(click)go c.publisher.Publish(click)ctx.JSON(http.StatusOK, ClickResponse{    ClickUUID:    click.ClickUUID,    TrackingLink: click.TrackingLink,})Save \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d \u043a\u0430\u043a func Save(c *CampaignClick) error. \u0422\u043e \u0435\u0441\u0442\u044c \u043f\u043e \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442\u0443 \u043c\u043e\u0436\u0435\u0442 \u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0443. \u0410 \u043c\u044b \u0435\u0451 \u043f\u0440\u043e\u0441\u0442\u043e \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u0435\u043c \u0438 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0443 200 OK, \u043a\u0430\u043a \u0431\u0443\u0434\u0442\u043e \u0432\u0441\u0451 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043b\u043e\u0441\u044c.\u0421\u0435\u0433\u043e\u0434\u043d\u044f in-memory \u2014 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0432\u0441\u0435\u0433\u0434\u0430 nil. \u041d\u043e \u0437\u0430\u0432\u0442\u0440\u0430 \u043a\u0442\u043e-\u0442\u043e \u0437\u0430\u043c\u0435\u043d\u0438\u0442 \u043d\u0430 PostgreSQL\/Redis\/Kafka, Save \u043d\u0430\u0447\u043d\u0451\u0442 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0435 \u043e\u0448\u0438\u0431\u043a\u0438, \u0430 handler \u043a\u0430\u043a \u043d\u0438 \u0432 \u0447\u0451\u043c \u043d\u0435 \u0431\u044b\u0432\u0430\u043b\u043e \u0440\u0430\u043f\u043e\u0440\u0442\u0443\u0435\u0442 \u201c\u0443\u0441\u043f\u0435\u0445\u201d. \u041a\u043b\u0438\u0435\u043d\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 clickUUID, \u0438\u0434\u0451\u0442 \u043f\u043e tracking link, \u043d\u043e \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0438 \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u0438\u043a\u0430 \u043d\u0435\u0442. Hello, broken funnel.\u0412 Go 1.26 \u0437\u0430\u0432\u0435\u0437\u043b\u0438 errors.AsType[T] \u2014 \u0442\u0438\u043f\u043e\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 generic-\u0432\u0430\u0440\u0438\u0430\u043d\u0442 errors.As. \u0423\u0434\u043e\u0431\u043d\u043e \u0434\u043b\u044f \u0447\u0438\u0441\u0442\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0434\u043e\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u0448\u0438\u0431\u043e\u043a:if err := c.repo.Save(ctx.Request.Context(), click); err != nil {    if dbErr, ok := errors.AsType[*DBError](err); ok &amp;&amp; dbErr.Retriable {        \/\/ retry \u0438\u043b\u0438 fallback    }    log.Printf(&#171;save click failed: %v&#187;, err)    ctx.JSON(http.StatusInternalServerError, gin.H{&#171;error&#187;: &#171;internal&#187;})    return}\u041c\u043e\u0440\u0430\u043b\u044c: \u0415\u0441\u043b\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 error \u2014 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0439 \u0435\u0433\u043e. \u0412\u0441\u0435\u0433\u0434\u0430. \u0414\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u201c\u0441\u0435\u0439\u0447\u0430\u0441 \u043e\u043d \u0432\u0441\u0435\u0433\u0434\u0430 nil\u201d. \u0421\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0430 \u2014 \u044d\u0442\u043e \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442 \u043d\u0430 \u0431\u0443\u0434\u0443\u0449\u0435\u0435. errcheck linter \u0432 CI \u0437\u0430\u043a\u0440\u043e\u0435\u0442 90% \u0442\u0430\u043a\u0438\u0445 \u0434\u044b\u0440 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438.12. \u0412\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f? \u041d\u0435, \u043d\u0435 \u0441\u043b\u044b\u0448\u0430\u043b\u0438\u0421\u043c\u043e\u0442\u0440\u044e handler:func (c *ClickController) Post(ctx *gin.Context) {    var req struct {        UserUUID     string `json:&#187;user_uuid&#187;`        CampaignUUID string `json:&#187;campaign_uuid&#187;`        Country      string `json:&#187;country&#187;`    }    _ = ctx.ShouldBindJSON(&amp;req)    \/\/ \u0434\u0430\u043b\u044c\u0448\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c req&#8230;}\u0417\u0430\u043c\u0435\u0442\u0438\u043b\u0438?  = ctx.ShouldBindJSON(&amp;req). \u041e\u0448\u0438\u0431\u043a\u0430 \u044f\u0432\u043d\u043e \u043f\u0440\u043e\u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0430 \u0447\u0435\u0440\u0435\u0437 &#171;_ &#171;. \u0422\u043e \u0435\u0441\u0442\u044c \u0435\u0441\u043b\u0438 \u043f\u0440\u0438\u0434\u0451\u0442 \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u044b\u0439 JSON, \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u043f\u0443\u0441\u0442\u0443\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443, \u0430 \u043f\u043e\u0442\u043e\u043c \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0437\u0430\u043f\u0438\u0441\u044c \u0441 \u043f\u0443\u0441\u0442\u044b\u043c\u0438 UUID\u2019\u0430\u043c\u0438. \u0417\u0430\u0442\u0435\u043c \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c tracking link https:\/\/tracker.example\/click?campaign=&amp;user= \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0443. Hello broken analytics.\u0418 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u043d\u0435\u0442 \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0438\u043a\u0430\u043a\u043e\u0439. UUID \u043c\u043e\u0433 \u0431\u044b\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u043e\u0439 &#171;drop table users&#187;, \u0441\u0442\u0440\u0430\u043d\u0430 \u2014 \u043f\u0443\u0441\u0442\u043e\u0439, \u0438\u043b\u0438 &#171;zz&#187;, \u0438\u043b\u0438 &#171;\ud83c\uddf7\ud83c\uddfa&#187;. \u0412\u0441\u0451 \u044d\u0442\u043e \u0443\u043b\u0435\u0442\u0438\u0442 \u0432 \u0411\u0414 (\u043a\u043e\u0433\u0434\u0430 in-memory \u0437\u0430\u043c\u0435\u043d\u044f\u0442 \u043d\u0430 \u043f\u043e\u0441\u0442\u0433\u0440\u0435\u0441) \u0438 \u0432 metrics.\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u2014 Gin \u0443\u043c\u0435\u0435\u0442 \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0430\u043c \u0447\u0435\u0440\u0435\u0437 binding \u0442\u0435\u0433\u0438:func (c *ClickController) Post(ctx *gin.Context) {    var req struct {        UserUUID     string `json:&#187;user_uuid&#187;     binding:&#187;required,uuid&#187;`        CampaignUUID string `json:&#187;campaign_uuid&#187; binding:&#187;required,uuid&#187;`        Country      string `json:&#187;country&#187;       binding:&#187;required,len=2,alpha&#187;`    }    if err := ctx.ShouldBindJSON(&amp;req); err != nil {        ctx.JSON(http.StatusBadRequest, gin.H{&#171;error&#187;: err.Error()})        return    }    \/\/ \u0434\u0430\u043b\u044c\u0448\u0435 \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0432\u0435\u0440\u044f\u0442\u044c req}\u041c\u043e\u0440\u0430\u043b\u044c: \u041b\u044e\u0431\u043e\u0439 external input \u2014 \u044d\u0442\u043e \u0432\u0440\u0430\u0436\u0434\u0435\u0431\u043d\u0430\u044f \u0441\u0440\u0435\u0434\u0430. \u0412\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0439 \u043d\u0430 \u0433\u0440\u0430\u043d\u0438\u0446\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b. \u0418 \u041d\u0418\u041a\u041e\u0413\u0414\u0410 \u043d\u0435 \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u0439 \u043e\u0448\u0438\u0431\u043a\u0438 \u0447\u0435\u0440\u0435\u0437 &#171;_&#187;. \u042d\u0442\u043e \u0432\u0441\u0435\u0433\u0434\u0430 \u0441\u0440\u0430\u0437\u0443 \u0442\u0440\u0438\u0433\u0433\u0435\u0440 \u043d\u0430 code review: \u043b\u0438\u0431\u043e \u044f\u0432\u043d\u043e \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u043e\u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0430 \u0438 \u044d\u0442\u043e \u041e\u041a (\u0438 \u0442\u043e\u0433\u0434\u0430 \u043d\u0443\u0436\u0435\u043d \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439 \u2014 \u043f\u043e\u0447\u0435\u043c\u0443), \u043b\u0438\u0431\u043e \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u043e\u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0430 \u0438 \u044d\u0442\u043e \u041d\u0415 \u041e\u041a. \u0422\u0440\u0435\u0442\u044c\u0435\u0433\u043e \u043d\u0435 \u0434\u0430\u043d\u043e.13. Country \u0431\u0435\u0437 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043d\u0430 ISO 3166-1\u0414\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c binding:&#187;len=2,alpha&#187;, \u044d\u0442\u043e \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442 &#171;XQ&#187;, &#171;ZZ&#187;, &#171;AA&#187; \u2014 \u0444\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e 2 \u0431\u0443\u043a\u0432\u044b, \u043d\u043e \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u043a\u043e\u0434\u044b \u0441\u0442\u0440\u0430\u043d. \u0412 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0435 \u043f\u043e\u0442\u043e\u043c \u044d\u0442\u0438 \u201c\u0441\u0442\u0440\u0430\u043d\u044b\u201d \u0441\u0438\u0434\u044f\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u043e\u0439 \u0438 \u043f\u043e\u0440\u0442\u044f\u0442 \u0433\u0440\u0430\u0444\u0438\u043a\u0438.\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u2014 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0439 validator \u0441 whitelist\u2019\u043e\u043c:import &#171;github.com\/biter777\/countries&#187;func validateCountry(fl validator.FieldLevel) bool {    code := fl.Field().String()    return countries.ByName(code).IsValid()}\/\/ \u043f\u0440\u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438:if v, ok := binding.Validator.Engine().(*validator.Validate); ok {    v.RegisterValidation(&#171;iso3166&#187;, validateCountry)}\/\/ \u0432 struct&#8217;\u0435:Country string `binding:&#187;required,iso3166&#8243;`\u041b\u0438\u0431\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c enum\u2019\u043e\u043c \u0438 \u043f\u0430\u0440\u0441\u0438\u0442\u044c:type CountryCode stringfunc (c *CountryCode) UnmarshalJSON(data []byte) error {    var s string    if err := json.Unmarshal(data, &amp;s); err != nil {        return err    }    if !isValidISO3166(s) {        return fmt.Errorf(&#171;invalid country code: %q&#187;, s)    }    *c = CountryCode(s)    return nil}\u041c\u043e\u0440\u0430\u043b\u044c: \u201c\u042d\u0442\u043e \u0436\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u0442\u0440\u043e\u043a\u0430\u201d \u2014 \u0433\u043b\u0430\u0432\u043d\u044b\u0439 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u043c\u0443\u0441\u043e\u0440\u0430 \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u0445. \u0415\u0441\u043b\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438\u0437 \u043a\u043e\u043d\u0435\u0447\u043d\u043e\u0433\u043e \u043d\u0430\u0431\u043e\u0440\u0430 \u2014 \u0437\u0430\u0432\u0435\u0434\u0438 enum \u0438\u043b\u0438 \u0432\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440. Domain types &gt; primitive types.14. Tracking link \u0441\u043e\u0431\u0440\u0430\u043d \u0441\u0442\u0440\u043e\u043a\u043e\u0439 \u2014 \u0430 \u0442\u0430\u043c \u0431\u0430\u0433\u0412\u043d\u0438\u043c\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0441\u043c\u043e\u0442\u0440\u044e \u043d\u0430:func generateTrackingLink(campaignUUID, userUUID string) string {    return &#171;https:\/\/tracker.example\/click?campaign=&#187; + campaignUUID + &#171;?user=&#187; + userUUID}\u0412\u0438\u0434\u0438\u0442\u0435? \u0414\u0432\u0430 \u0437\u043d\u0430\u043a\u0430 ? \u0432 URL. \u0414\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c ? \u0434\u043b\u044f \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u0438 &amp; \u0434\u043b\u044f \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0445. \u0422\u043e \u0435\u0441\u0442\u044c \u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0432 ?campaign=&#8230;?user=&#8230; \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 campaign \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 &lt;uuid&gt;?user=&lt;uuid&gt;, \u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 user \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 tracker\u2019\u0430.\u042d\u0442\u043e \u0434\u0430\u0436\u0435 \u043d\u0435 \u0431\u0430\u0433 concurrency \u0438\u043b\u0438 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438. \u042d\u0442\u043e \u0431\u0438\u0437\u043d\u0435\u0441-\u0431\u0430\u0433: \u0442\u0440\u0435\u043a\u0435\u0440 \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u0442 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043b\u0438\u043a \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e. \u0414\u0435\u043d\u044c\u0433\u0438 \u0440\u0435\u043a\u043b\u0430\u043c\u043e\u0434\u0430\u0442\u0435\u043b\u0435\u0439 \u0443\u043b\u0435\u0442\u0430\u044e\u0442 \u0432 \u043d\u0438\u043a\u0443\u0434\u0430, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0430 \u0431\u0438\u0442\u0430\u044f.\u041c\u043e\u0440\u0430\u043b\u044c: \u041d\u0435 \u043a\u043b\u0435\u0439 URL\u2019\u044b \u0438 SQL-\u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0441\u0442\u0440\u043e\u043a\u0430\u043c\u0438. \u0414\u043b\u044f URL \u2014 net\/url, \u0434\u043b\u044f SQL \u2014 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b. \u042d\u0442\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u043e 1990-\u0445, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043f\u043e\u0447\u0435\u043c\u0443-\u0442\u043e \u0432\u0441\u0451 \u0435\u0449\u0451 \u043d\u0430\u0440\u0443\u0448\u0430\u044e\u0442 \u0432 2026-\u043c.15. URL \u0431\u0435\u0437 escaping \u2014 open \u0434\u043b\u044f injection\u0414\u043e\u043f\u0443\u0441\u0442\u0438\u043c, \u043c\u044b \u043f\u043e\u0447\u0438\u043d\u0438\u043b\u0438 \u043f\u0440\u043e\u0448\u043b\u044b\u0439 \u0431\u0430\u0433 \u0438 \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u043c URL \u0440\u0443\u043a\u0430\u043c\u0438: ?campaign= + &amp;user=. \u0412\u0441\u0451 \u0435\u0449\u0451 \u043f\u043b\u043e\u0445\u043e: \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043d\u0435 \u044d\u043a\u0440\u0430\u043d\u0438\u0440\u0443\u044e\u0442\u0441\u044f. \u0415\u0441\u043b\u0438 \u0432 userUUID \u043f\u0440\u0438\u043b\u0435\u0442\u0438\u0442 \u0441\u0442\u0440\u043e\u043a\u0430 \u0441 &amp; \u0438\u043b\u0438 = (\u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u0442 \u0441\u0430\u043c\u044b\u0439 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u0432\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440 \u0438\u0437 \u0431\u0430\u0433\u0430 #12), \u043c\u044b \u0441\u043b\u043e\u043c\u0430\u0435\u043c \u0440\u0430\u0437\u0431\u043e\u0440 query string \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 tracker\u2019\u0430:?campaign=abc&amp;user=evil&amp;fake=injected\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 user \u0441\u0442\u0430\u043b evil, \u0430 \u0432 URL \u043f\u043e\u044f\u0432\u0438\u043b\u0441\u044f \u043b\u0435\u0432\u044b\u0439 fake. \u0415\u0441\u043b\u0438 tracker \u044d\u0442\u043e\u043c\u0443 \u0434\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u2014 \u0443 \u043d\u0430\u0441 injection.\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u2014 net\/url:import &#171;net\/url&#187;func generateTrackingLink(campaignUUID, userUUID string) string {    u, _ := url.Parse(&#171;https:\/\/tracker.example\/click&#187;)    q := u.Query()    q.Set(&#171;campaign&#187;, campaignUUID)    q.Set(&#171;user&#187;, userUUID)    u.RawQuery = q.Encode()  \/\/ \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 escape    return u.String()}url.Values.Encode() \u0441\u0434\u0435\u043b\u0430\u0435\u0442 URL-escaping \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438. \u0417\u0430\u043e\u0434\u043d\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u0440\u0430\u0441\u0441\u0442\u0430\u0432\u0438\u0442 ? \u0438 &amp;.\u041c\u043e\u0440\u0430\u043b\u044c: \u041b\u044e\u0431\u044b\u0435 user-controlled \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f, \u043f\u043e\u043f\u0430\u0434\u0430\u044e\u0449\u0438\u0435 \u0432 URL\/SQL\/HTML \u2014 \u0434\u043e\u043b\u0436\u043d\u044b \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 escape. net\/url, database\/sql (placeholders), html\/template (\u0430 \u043d\u0435 text\/template) \u2014 \u044d\u0442\u043e \u0431\u0430\u0437\u043e\u0432\u0430\u044f \u0433\u0438\u0433\u0438\u0435\u043d\u0430. \u041d\u0435 \u0434\u0435\u043b\u0430\u0439 \u0440\u0443\u043a\u0430\u043c\u0438 \u0442\u043e, \u0447\u0442\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u0430\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u0435\u043b\u0430\u0435\u0442 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e.16. Status 200 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0440\u0435\u0441\u0443\u0440\u0441\u0430Handler \u0432\u0441\u0435\u0433\u0434\u0430 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 http.StatusOK:ctx.JSON(http.StatusOK, ClickResponse{&#8230;})\u0418 \u0434\u043b\u044f \u043d\u043e\u0432\u043e\u0433\u043e \u043a\u043b\u0438\u043a\u0430, \u0438 \u0434\u043b\u044f \u0434\u0435\u0434\u0443\u043f\u043b\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430 \u0438\u0437 \u043a\u0435\u0448\u0430. \u0421\u0435\u043c\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u044d\u0442\u043e \u043d\u0435\u0432\u0435\u0440\u043d\u043e:\u041d\u043e\u0432\u044b\u0439 \u043a\u043b\u0438\u043a \u2192 \u0441\u043e\u0437\u0434\u0430\u043d \u0440\u0435\u0441\u0443\u0440\u0441 \u2192 201 Created\u0414\u0435\u0434\u0443\u043f\u043b\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u2192 \u0441\u0442\u0430\u0440\u044b\u0439 \u0440\u0435\u0441\u0443\u0440\u0441 \u2192 200 OK (\u0438\u043b\u0438 \u0434\u0430\u0436\u0435 409 Conflict, \u0435\u0441\u043b\u0438 \u0445\u043e\u0442\u0438\u043c \u044f\u0432\u043d\u043e \u0441\u0438\u0433\u043d\u0430\u043b\u0438\u0442\u044c \u0434\u0443\u0431\u043b\u044c)\u041a\u043b\u0438\u0435\u043d\u0442\u0443 \u043e\u0431\u044b\u0447\u043d\u043e \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e (\u043e\u043d \u0441\u043c\u043e\u0442\u0440\u0438\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 2xx), \u043d\u043e \u043a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0442\u0435\u043b\u0435\u0436\u043a\u0430 observability \u2014 \u0433\u0440\u0430\u0444\u0438\u043a\u0438 \u201c\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043d\u043e\u0432\u044b\u0445 vs \u0434\u0443\u0431\u043b\u0438\u043a\u0430\u0442\u043e\u0432\u201d \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043d\u0435\u043b\u044c\u0437\u044f \u0431\u0435\u0437 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u043c\u0435\u0442\u0440\u0438\u043a\u0438, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e HTTP-\u043b\u043e\u0433\u0438 \u043d\u0435 \u0440\u0430\u0437\u043b\u0438\u0447\u0430\u044e\u0442 \u044d\u0442\u0438 \u0441\u043b\u0443\u0447\u0430\u0438.\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e:if cached, ok := c.repo.GetRecent(&#8230;); ok {    ctx.JSON(http.StatusOK, toResponse(cached))    return}\/\/ &#8230; \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0433\u043ectx.JSON(http.StatusCreated, toResponse(click))\u041c\u043e\u0440\u0430\u043b\u044c: HTTP status codes \u2014 \u044d\u0442\u043e API. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439 \u0438\u0445 \u043f\u043e \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044e. 201 Created \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f, 204 No Content \u0434\u043b\u044f DELETE \u0431\u0435\u0437 \u0442\u0435\u043b\u0430, 409 Conflict \u0434\u043b\u044f \u0434\u0443\u0431\u043b\u0438\u043a\u0430\u0442\u043e\u0432. \u042d\u0442\u043e \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u0430\u044f \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f \u0434\u043b\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u0438 \u043c\u0435\u0442\u0440\u0438\u043a\u0430 \u0434\u043b\u044f observability.17. Context? \u041a\u0430\u043a\u043e\u0439 context?\u0413\u0434\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0436\u0438\u0437\u043d\u0435\u043d\u043d\u044b\u043c \u0446\u0438\u043a\u043b\u043e\u043c? \u041d\u0438\u0433\u0434\u0435. \u041c\u0435\u0442\u043e\u0434 Save \u043d\u0435 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 context.Context. Publish \u0442\u043e\u0436\u0435. \u0415\u0441\u043b\u0438 \u0437\u0430\u043f\u0440\u043e\u0441 \u043e\u0442\u043c\u0435\u043d\u0438\u043b\u0441\u044f (\u043a\u043b\u0438\u0435\u043d\u0442 \u043e\u0442\u0432\u0430\u043b\u0438\u043b\u0441\u044f) \u2014 \u043c\u044b \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u043a\u043b\u0438\u043a \u0438 \u043f\u043e\u043f\u044b\u0442\u0430\u0435\u043c\u0441\u044f \u043e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u0442\u044c.\u0414\u043b\u044f in-memory &#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-479265","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/479265","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=479265"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/479265\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=479265"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=479265"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=479265"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}