{"id":368096,"date":"2024-05-21T03:35:23","date_gmt":"2024-05-21T03:35:23","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=368096"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=368096","title":{"rendered":"<span>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 Postgres \u0438 JSONB \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0442\u043e\u0432\u0430\u0440\u043e\u0432, \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0441\u0442\u0438\u043a \u0438 \u0446\u0435\u043d<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442! \u041c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 \u0410\u043b\u0435\u043a\u0441\u0430\u043d\u0434\u0440, \u0432 \u0434\u0430\u043d\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u044f \u043d\u0430 \u0444\u0440\u0438\u043b\u0430\u043d\u0441\u0435 \u0437\u0430\u043d\u0438\u043c\u0430\u044e\u0441\u044c \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u043c \u043f\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e\u0433\u043e \u043c\u0430\u0440\u043a\u0435\u0442\u043f\u043b\u0435\u0439\u0441\u0430. \u0412 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0440\u0430\u0431\u043e\u0442\u044b \u043c\u044b \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0438\u0441\u044c \u0441 \u0434\u0430\u043b\u0435\u043a\u043e \u043d\u0435 \u043d\u043e\u0432\u043e\u0439 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043e\u0439 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u0442\u043e\u0432\u0430\u0440\u0430\u0445, \u0438\u043c\u0435\u044e\u0449\u0438\u0445 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0441\u0442\u0438\u043a\u0438 \u0438 \u0446\u0435\u043d\u0443, \u0437\u0430\u0432\u0438\u0441\u044f\u0449\u0443\u044e \u043e\u0442 \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0441\u0442\u0438\u043a. \u041d\u0430 \u043d\u0430\u0448\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0438\u043d\u0446\u0438\u043f CQRS: \u0437\u0430\u043f\u0438\u0441\u044c \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432 Postgres, \u0447\u0442\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0438\u0437 OpenSearch, \u0430 \u0434\u0430\u043d\u043d\u044b\u0435 \u043c\u0435\u0436\u0434\u0443 \u043d\u0438\u043c\u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u044e\u0442\u0441\u044f \u043f\u043e \u0448\u0438\u043d\u0435, \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u043d\u0430 Kafka. \u0422\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043e\u0431\u0443\u0441\u043b\u043e\u0432\u0438\u043b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0440\u0435\u043b\u044f\u0446\u0438\u043e\u043d\u043d\u043e\u0439 \u0411\u0414 \u0434\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043d\u0435\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0439 \u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438. <\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u0443\u0432\u0438\u0434\u0435\u0442\u044c, \u043f\u043e\u0447\u0435\u043c\u0443 \u044d\u0442\u0430 \u0437\u0430\u0434\u0430\u0447\u0430 \u043d\u0435 \u043d\u0430\u043a \u043f\u0440\u043e\u0441\u0442\u0430, \u043a\u0430\u043a \u043a\u0430\u0436\u0435\u0442\u0441\u044f \u0441 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0432\u0437\u0433\u043b\u044f\u0434\u0430, \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u043c, \u0447\u0442\u043e \u0432 \u043d\u0430\u0448\u0435\u043c \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0435 \u0435\u0441\u0442\u044c \u0444\u0443\u0442\u0431\u043e\u043b\u043a\u0438 \u043c\u0443\u0436\u0441\u043a\u0438\u0435 \u0432\u0441\u0435\u0433\u043e \u0441 \u0434\u0432\u0443\u043c\u044f \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430\u043c\u0438: \u0446\u0432\u0435\u0442  \u0438 \u0440\u0430\u0437\u043c\u0435\u0440. \u041c\u044b \u0445\u043e\u0442\u0438\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0442\u043e\u0432\u0430\u0440 \u0441 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c &#171;\u0424\u0443\u0442\u0431\u043e\u043b\u043a\u0430 Junior Developer&#187;, \u043e\u043d\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 \u0441\u0438\u043d\u0435\u043c, \u043a\u0440\u0430\u0441\u043d\u043e\u043c \u0438 \u0437\u0435\u043b\u0435\u043d\u043e\u043c \u0446\u0432\u0435\u0442\u0430\u0445, \u0438 \u043a\u0430\u0436\u0434\u044b\u0439 \u0446\u0432\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0432 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0440\u0430\u0437\u043c\u0435\u0440\u0430\u0445. \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u044f \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438, \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u043c, \u0447\u0442\u043e \u0446\u0435\u043d\u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0439 \u0444\u0443\u0442\u0431\u043e\u043b\u043a\u0438 \u0442\u0430\u043a\u0436\u0435 \u0432\u0430\u0440\u044c\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0446\u0432\u0435\u0442\u0430 \u0438 \u0440\u0430\u0437\u043c\u0435\u0440\u0430. \u041a\u0430\u043a \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u044d\u0442\u0443 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c \u0432 \u0440\u0435\u043b\u044f\u0446\u0438\u043e\u043d\u043d\u043e\u0439 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445, \u0441 \u0443\u0447\u0435\u0442\u043e\u043c \u0442\u043e\u0433\u043e, \u0447\u0442\u043e \u043f\u0440\u043e\u0434\u0430\u0432\u0435\u0446 \u0444\u0443\u0442\u0431\u043e\u043b\u043a\u0438 \u043c\u043e\u0436\u0435\u0442 \u0432 \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u043c\u043e\u043c\u0435\u043d\u0442 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b \u0434\u043b\u044f \u0441\u0432\u043e\u0435\u0433\u043e \u0442\u043e\u0432\u0430\u0440\u0430, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0433\u0430\u0431\u0430\u0440\u0438\u0442\u044b \u0443\u043f\u0430\u043a\u043e\u0432\u043a\u0438 \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438<br \/>(\u0434\u043b\u0438\u043d\u0430, \u0448\u0438\u0440\u0438\u043d\u0430, \u0432\u044b\u0441\u043e\u0442\u0430)? <\/p>\n<p>\u0421 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u044b, \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434: <a href=\"https:\/\/en.wikipedia.org\/wiki\/Entity%E2%80%93attribute%E2%80%93value_model\" rel=\"noopener noreferrer nofollow\">Entity-Attribute-Value<\/a>. \u041e\u043d \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0433\u0438\u0431\u043a\u043e \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c \u0441\u0432\u044f\u0437\u0438 \u043c\u0435\u0436\u0434\u0443 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044f\u043c\u0438, \u0438\u0445 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430\u043c\u0438 \u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u043c\u0438, \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043d\u043e\u0432\u044b\u0445 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438. \u041e\u0434\u043d\u0430\u043a\u043e \u0443 \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0430 \u0435\u0441\u0442\u044c \u0441\u0432\u043e\u0438 \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043a\u0438. \u041d\u0430 \u043d\u0438\u0445 \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0435 \u0431\u0443\u0434\u0443 &#8212; \u0432 \u0441\u0442\u0430\u0442\u044c\u0435: <a href=\"https:\/\/habr.com\/ru\/articles\/475178\/\" rel=\"noopener noreferrer nofollow\">&#171;\u0417\u0430\u043c\u0435\u043d\u0430 EAV \u043d\u0430 JSONB \u0432 PostgreSQL&#187;<\/a> \u043e\u043d\u0438 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u044b, \u0442\u0430\u043a\u0436\u0435 \u0442\u0430\u043c \u0435\u0441\u0442\u044c \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u0435 \u043f\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u043f\u0430\u043c\u044f\u0442\u0438 EAV \u0438 JSONB. <\/p>\n<p>\u042f \u0436\u0435 \u043f\u043e\u0441\u0442\u0430\u0440\u0430\u044e\u0441\u044c \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043d\u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435, \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u0438\u043f \u0434\u0430\u043d\u043d\u044b\u0445 JSONB \u0434\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0439, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0441\u043e\u043f\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u0437\u0430\u0434\u0430\u0447 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432 \u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u0439 Postgres \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 JSONB. \u041a\u0430\u043a \u0438 \u0432 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0445 \u043f\u043e\u0441\u0442\u0430\u0445, \u044f \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043b \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 <a href=\"https:\/\/github.com\/aasmc\/product-service-demo\" rel=\"noopener noreferrer nofollow\">\u0434\u0435\u043c\u043e \u043f\u0440\u043e\u0435\u043a\u0442<\/a>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u044b\u043b\u043e\u0436\u0438\u043b \u043d\u0430 Github. \u042f \u043d\u0435 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u044e, \u0447\u0442\u043e \u0434\u0430\u043d\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043e\u043f\u0442\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u043c, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0431\u0443\u0434\u0443 \u0440\u0430\u0434 \u043b\u044e\u0431\u043e\u0439 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u0438\u0432\u043d\u043e\u0439 \u043a\u0440\u0438\u0442\u0438\u043a\u0435. \u0418\u0442\u0430\u043a, \u043f\u0440\u0438\u0441\u0442\u0443\u043f\u0438\u043c.<\/p>\n<h2>\u041c\u043e\u0434\u0435\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0445<\/h2>\n<h4>Product \u0438 ProductVariant. <\/h4>\n<p>\u041e\u0431\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0442\u043e\u0432\u0430\u0440\u0435 \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 products:<\/p>\n<pre><code class=\"sql\">CREATE TABLE products(     id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,     shop_id BIGINT REFERENCES shops(id),     category_id BIGINT REFERENCES categories(id),     name TEXT NOT NULL,     description TEXT NOT NULL,     created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,     updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP );<\/code><\/pre>\n<p>\u0414\u0435\u0442\u0430\u043b\u0438 \u043e \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430\u0445 \u0442\u043e\u0432\u0430\u0440\u0430, \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c\u044b\u0445 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u043a\u043e\u043c\u0431\u0438\u043d\u0430\u0446\u0438\u0439 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432,  \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 product_variants:<\/p>\n<pre><code class=\"sql\">CREATE TABLE product_variants (     id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,     variant_name TEXT NOT NULL,     product_id BIGINT REFERENCES products(id) ON DELETE CASCADE,     attribute_collection JSONB,     image_collection JSONB,     sku_collection JSONB,     price DECIMAL NOT NULL,     created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,     updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP );<\/code><\/pre>\n<p>\u0414\u043b\u044f \u043d\u0430\u0441 \u0438\u043d\u0442\u0435\u0440\u0435\u0441 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442 \u0442\u0440\u0438 \u043f\u043e\u043b\u044f:<\/p>\n<ul>\n<li>\n<p>image_collection<\/p>\n<\/li>\n<li>\n<p>attribute_collection<\/p>\n<\/li>\n<li>\n<p>sku_collection<\/p>\n<\/li>\n<\/ul>\n<h4>image_collection<\/h4>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0442\u043e\u0432\u0430\u0440\u0430 \u043c\u043e\u0436\u0435\u0442 \u0438\u043c\u0435\u0442\u044c \u043e\u0434\u043d\u0443 \u0438\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043c\u044b \u0445\u0440\u0430\u043d\u0438\u043c \u0442\u043e\u043b\u044c\u043a\u043e url \u043d\u0430 \u0444\u043e\u0442\u043e, \u0438 \u0444\u043b\u0430\u0433, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043b\u0438 \u044d\u0442\u043e \u0444\u043e\u0442\u043e \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u043c \u0432 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438. <code>image_collection<\/code> \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043a\u043b\u0430\u0441\u0441\u043e\u043c:<\/p>\n<pre><code class=\"kotlin\">data class AppImage(     val url: String,     val isPrimary: Boolean = false )  data class ImageCollection(     val images: MutableSet&lt;AppImage> = hashSetOf() )<\/code><\/pre>\n<h4>attribute_collection<\/h4>\n<p>\u042d\u0442\u043e \u0431\u043e\u043b\u0435\u0435 \u0441\u043b\u043e\u0436\u043d\u044b\u0439 \u0441\u043b\u0443\u0447\u0430\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e. \u0410\u0442\u0440\u0438\u0431\u0443\u0442\u044b \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u044b \u043d\u0430 \u0442\u0438\u043f\u044b: \u0441\u0442\u0440\u043e\u043a\u043e\u0432\u044b\u0439, \u0447\u0438\u0441\u043b\u043e\u0432\u043e\u0439, \u0446\u0432\u0435\u0442\u043e\u0432\u043e\u0439 \u0438 \u0441\u043e\u0441\u0442\u0430\u0432\u043d\u043e\u0439. \u0417\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432 \u0442\u0430\u043a\u0436\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u044f\u044e\u0442\u0441\u044f \u043f\u043e \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u044b\u043c \u0442\u0438\u043f\u0430\u043c, \u043a\u0440\u043e\u043c\u0435 \u0441\u043e\u0441\u0442\u0430\u0432\u043d\u043e\u0433\u043e. \u0412 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0435, \u0432 \u043f\u043e\u043b\u0435 JSONB \u043c\u043e\u0436\u043d\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c <code>Map&lt;String, Any><\/code>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0438 \u0431\u0443\u0434\u0435\u0442 \u0442\u0435\u043c JSON \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u043c, \u043f\u0440\u0438\u0445\u043e\u0434\u044f\u0449\u0438\u043c \u0441 \u0444\u0440\u043e\u043d\u0442\u0430 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0442\u043e\u0432\u0430\u0440\u0430. \u041e\u0434\u043d\u0430\u043a\u043e \u0442\u0430\u043a\u043e\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u043b\u0438\u0448\u0430\u0435\u0442 \u043d\u0430\u0441 \u0441\u0442\u0440\u043e\u0433\u043e\u0439 \u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u0447\u0440\u0435\u0432\u0430\u0442 \u043e\u0448\u0438\u0431\u043a\u0430\u043c\u0438. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 Jackson \u043f\u043e \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0434\u0435\u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438\u0435\u0440\u0430\u0440\u0445\u0438\u0438 \u043a\u043b\u0430\u0441\u0441\u043e\u0432, \u0432 \u0447\u0430\u0441\u0442\u043d\u043e\u0441\u0442\u0438 \u0430\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u044f\u043c\u0438 <code>@JsonTypeInfo<\/code> \u0438 <code>@JsonSubTypes<\/code>, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0449\u0438\u043c\u0438\u0441\u044f \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u0430\u0442\u044c Jackson, \u0441 \u043a\u0430\u043a\u0438\u043c \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u043a\u043b\u0430\u0441\u0441\u043e\u043c \u0441\u0435\u0439\u0447\u0430\u0441 \u0438\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430. \u041f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c <a href=\"https:\/\/www.baeldung.com\/jackson-inheritance\" rel=\"noopener noreferrer nofollow\">\u0442\u0443\u0442<\/a>.<\/p>\n<p>\u0418\u0442\u0430\u043a, \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432 \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u0441\u043b\u0435\u0434\u0443\u0431\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"kotlin\">enum class AttributeValueType(     @field:JsonValue     val value: String ) {     STRING_TYPE(\"string_type\"),     NUMERIC_TYPE(\"numeric_type\"),     COLOR_TYPE(\"color_type\") }  @JsonTypeInfo(     use = JsonTypeInfo.Id.NAME,     visible = true,     include = JsonTypeInfo.As.EXISTING_PROPERTY,     property = \"type\" ) @JsonSubTypes(     *arrayOf(         JsonSubTypes.Type(             value = StringAttributeValueDto::class,             name = \"string_type\"         ),         JsonSubTypes.Type(             value = NumericAttributeValueDto::class,             name = \"numeric_type\"         ),         JsonSubTypes.Type(             value = ColorAttributeValueDto::class,             name = \"color_type\"         )     ) ) sealed class AttributeValueDto(     open val type: AttributeValueType )  data class StringAttributeValueDto(     val stringValue: String,     val stringRuValue: String?,     override val type: AttributeValueType = AttributeValueType.STRING_TYPE ) : AttributeValueDto(type)  data class NumericAttributeValueDto(     val numValue: Double,     val numRuValue: Double?,     val numUnit: String,     override val type: AttributeValueType = AttributeValueType.NUMERIC_TYPE ) : AttributeValueDto(type)  data class ColorAttributeValueDto(     val colorValue: String,     val colorHex: String,     override val type: AttributeValueType = AttributeValueType.COLOR_TYPE ) : AttributeValueDto(type)<\/code><\/pre>\n<p>\u0421\u0430\u043c\u0438 \u0436\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u044b \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0438\u0435\u0440\u0430\u0440\u0445\u0438\u0435\u0439 \u043a\u043b\u0430\u0441\u0441\u043e\u0432, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043a\u043b\u044e\u0447\u0435\u0432\u043e\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0435 \u0442\u0438\u043f\u043e\u0432 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432 \u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439:<\/p>\n<pre><code class=\"kotlin\">enum class AttributeType(     @field:JsonValue     val value: String ) {     STRING_ATTR(\"string\"),     NUMERIC_ATTR(\"numeric\"),     COLOR_ATTR(\"color\"),     COMPOSITE_ATTR(\"composite\") }  @JsonTypeInfo(     use = JsonTypeInfo.Id.NAME,     include = JsonTypeInfo.As.EXISTING_PROPERTY,     property = \"type\",     visible = true ) @JsonSubTypes(*arrayOf(     JsonSubTypes.Type(         value = StringAttributeDto::class,         name = \"string\"     ),     JsonSubTypes.Type(         value = NumericAttributeDto::class,         name = \"numeric\"     ),     JsonSubTypes.Type(         value = ColorAttributeDto::class,         name = \"color\"     ),     JsonSubTypes.Type(         value = CompositeAttributeDto::class,         name = \"composite\"     ) )) sealed class AttributeDto(     open val id: String?,     open val attributeName: String,     open val shortName: String,     open val isFaceted: Boolean,     open val type: AttributeType,     open val createdAt: LocalDateTime?,     open val isRequired: Boolean? = null,     open val availableValues: List&lt;AttributeValueDto> )  data class StringAttributeDto(     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val id: String? = null,     override val attributeName: String,     override val shortName: String,     override val isFaceted: Boolean,     override val type: AttributeType = AttributeType.STRING_ATTR,     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val isRequired: Boolean? = null,     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val createdAt: LocalDateTime? = null,     override val availableValues: MutableList&lt;StringAttributeValueDto>, ) : AttributeDto(id, attributeName, shortName, isFaceted, type, createdAt, isRequired, availableValues)  data class NumericAttributeDto(     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val id: String? = null,     override val attributeName: String,     override val shortName: String,     override val isFaceted: Boolean,     override val type: AttributeType = AttributeType.NUMERIC_ATTR,     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val isRequired: Boolean? = null,     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val createdAt: LocalDateTime? = null,     override val availableValues: MutableList&lt;NumericAttributeValueDto>, ) : AttributeDto(id, attributeName, shortName, isFaceted, type, createdAt, isRequired, availableValues)  data class ColorAttributeDto(     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val id: String? = null,     override val attributeName: String,     override val shortName: String,     override val isFaceted: Boolean,     override val type: AttributeType = AttributeType.COLOR_ATTR,     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val isRequired: Boolean? = null,     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val createdAt: LocalDateTime? = null,     override val availableValues: MutableList&lt;ColorAttributeValueDto>, ) : AttributeDto(id, attributeName, shortName, isFaceted, type, createdAt, isRequired, availableValues)  data class CompositeAttributeDto(     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val id: String? = null,     override val attributeName: String,     override val shortName: String,     override val isFaceted: Boolean,     override val type: AttributeType = AttributeType.COMPOSITE_ATTR,     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val isRequired: Boolean? = null,     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val createdAt: LocalDateTime? = null,     val subAttributes: List&lt;AttributeDto> ): AttributeDto(id, attributeName, shortName, isFaceted, type, createdAt, isRequired, listOf())<\/code><\/pre>\n<p>\u041e\u0441\u043e\u0431\u043d\u044f\u043a\u043e\u043c \u0441\u0442\u043e\u0438\u0442 \u0441\u043e\u0441\u0442\u0430\u0432\u043d\u043e\u0439 \u0442\u0438\u043f \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0430\u0442\u0440\u0438\u0431\u0443\u0442 &#171;\u0413\u0430\u0431\u0430\u0440\u0438\u0442\u044b&#187;, \u043c\u043e\u0436\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0432 \u0441\u0435\u0431\u0435 \u0442\u0430\u043a\u0438\u0435 \u043f\u043e\u0434-\u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b, \u043a\u0430\u043a: &#171;\u0414\u043b\u0438\u043d\u0430&#187;, &#171;\u0428\u0438\u0440\u0438\u043d\u0430&#187; \u0438 &#171;\u0412\u044b\u0441\u043e\u0442\u0430&#187;. \u0421\u0430\u043c \u0441\u043e\u0441\u0442\u0430\u0432\u043d\u043e\u0439 \u0430\u0442\u0440\u0438\u0431\u0443\u0442 \u043d\u0435 \u0438\u043c\u0435\u0435\u0442 \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0433\u043e \u0441\u043f\u0438\u0441\u043a\u0430 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439, \u0442\u0430\u043a \u043a\u0430\u043a \u0432\u0441\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0442 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u043c \u043f\u043e\u0434-\u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430\u043c. <\/p>\n<h4>sku_collection<\/h4>\n<p>\u041d\u0435\u043c\u0430\u043b\u043e\u0432\u0430\u0436\u043d\u043e\u0439 \u0447\u0430\u0441\u0442\u044c\u044e \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430 \u0442\u043e\u0432\u0430\u0440\u0430 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0435\u0433\u043e \u0430\u0440\u0442\u0438\u043a\u0443\u043b \u0438\u043b\u0438 <a href=\"https:\/\/www.shopify.com\/blog\/what-is-a-stock-keeping-unit\" rel=\"noopener noreferrer nofollow\">Stock Keeping Unit (SKU)<\/a>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u043e \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0443\u044e \u0435\u0434\u0438\u043d\u0438\u0446\u0443 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0442\u043e\u0432\u0430\u0440\u0430 \u0438 \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u0435\u0435 \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u043d\u0430 \u0441\u043a\u043b\u0430\u0434\u0435. <\/p>\n<p>\u0412 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u043a\u0430\u0436\u0434\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0442\u043e\u0432\u0430\u0440\u0430 \u0431\u0443\u0434\u0435\u0442 \u0438\u043c\u0435\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0430\u0440\u0442\u0438\u043a\u0443\u043b\u043e\u0432, \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0445 \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c \u0432\u0438\u0434\u0435:<\/p>\n<pre><code class=\"kotlin\">data class Sku(     val attrValue: String,     val price: BigDecimal,     val stock: Int,     val sku: String )  data class SkuCollection(     val attrName: String,     val skus: List&lt;Sku> )<\/code><\/pre>\n<p>\u041f\u0440\u0438 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 <code>SkuCollection<\/code>, \u043e\u0434\u0438\u043d \u0438\u0437 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432 \u0442\u043e\u0432\u0430\u0440\u0430 \u0431\u0443\u0434\u0435\u0442 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0449\u0438\u043c \u0434\u043b\u044f \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0439 \u0435\u0434\u0438\u043d\u0438\u0446\u044b \u0442\u043e\u0432\u0430\u0440\u0430 (\u044d\u0442\u043e\u0442 \u0430\u0442\u0440\u0438\u0431\u0443\u0442 \u0437\u0430\u0434\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0434\u0430\u0432\u0446\u043e\u043c). \u041f\u043e\u0441\u0442\u0430\u0440\u0430\u044e\u0441\u044c \u043f\u043e\u044f\u0441\u043d\u0438\u0442\u044c. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0442\u043e\u0432\u0430\u0440 &#171;\u0424\u0443\u0442\u0431\u043e\u043b\u043a\u0430 Junior Developer&#187;, \u0432 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430\u0445, \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c\u044b\u0445 \u0446\u0432\u0435\u0442\u0430\u043c\u0438, \u0438 \u043a\u0430\u0436\u0434\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0438\u043c\u0435\u0435\u0442 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u0432, \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0448\u0442\u0443\u043a \u043a\u0430\u0436\u0434\u043e\u0439 \u0444\u0443\u0442\u0431\u043e\u043b\u043a\u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0446\u0432\u0435\u0442\u0430, \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u0440\u0430\u0437\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f:<\/p>\n<ul>\n<li>\n<p>\u0441\u0438\u043d\u044f\u044f: XL &#8212; 2\u0448\u0442, XXL &#8212; 4\u0448\u0442<\/p>\n<\/li>\n<li>\n<p>\u0437\u0435\u043b\u0435\u043d\u0430\u044f: M &#8212; 3\u0448\u0442, S &#8212; 1\u0448\u0442<\/p>\n<\/li>\n<li>\n<p>\u043a\u0440\u0430\u0441\u043d\u0430\u044f: XS &#8212; 10\u0448\u0442<\/p>\n<\/li>\n<\/ul>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0432 \u044d\u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442, \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0449\u0438\u0439 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0443\u044e \u0435\u0434\u0438\u043d\u0438\u0446\u0443 \u0442\u043e\u0432\u0430\u0440\u0430 &#8212; \u0440\u0430\u0437\u043c\u0435\u0440, \u0438 \u0438\u043c\u043c\u0435\u043d\u043d\u043e \u044d\u0442\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0432 \u043f\u043e\u043b\u0435 <code>SkuCollection.attrName<\/code>. \u041f\u043e\u043b\u0435 <code>Sku.price<\/code> \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0446\u0435\u043d\u0443 \u0442\u043e\u0432\u0430\u0440\u0430 \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0435\u0433\u043e \u0430\u0440\u0442\u0438\u043a\u0443\u043b\u0430. \u041f\u043e\u043b\u0435 <code>Sku.stock<\/code> &#8212; \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0435\u0434\u0438\u043d\u0438\u0446 \u0442\u043e\u0432\u0430\u0440\u0430 \u043d\u0430 \u0441\u043a\u043b\u0430\u0434\u0435. \u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0443\u0441\u043b\u043e\u0436\u043d\u044f\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442, \u0441\u0430\u043c \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 Stock Keeping Unit &#8212; \u043f\u043e\u043b\u0435 <code>Sku.sku<\/code> \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u0438\u0437\u0432\u043d\u0435, \u0438 \u043d\u0430\u0448 \u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0435 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0435\u0433\u043e \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u0435 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438, \u0447\u0438\u0442\u0430\u0435\u043c\u043e\u0441\u0442\u0438 \u0438 \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f.<\/p>\n<h4>Attribute \u0438 Category<\/h4>\n<p>\u0418\u0442\u0430\u043a, \u0441\u0435\u0440\u0432\u0438\u0441 \u043c\u043e\u0436\u0435\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0442\u043e\u0432\u0430\u0440\u044b, \u0438\u0445 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b \u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0432 \u0443\u0434\u043e\u0431\u043d\u043e\u043c \u043d\u0430\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u0435, \u043d\u043e \u043a\u0430\u043a \u0431\u044b\u0442\u044c\u0441 \u0442\u0435\u043c, \u0447\u0442\u043e\u0431\u044b \u043d\u0430 \u0444\u0440\u043e\u043d\u0442\u0435 \u0431\u044b\u043b \u0443\u0434\u043e\u0431\u043d\u044b\u0439 UI \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0442\u043e\u0432\u0430\u0440\u043e\u0432 \u0438 \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e\u0431 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430\u0445? \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043c\u044b \u0445\u043e\u0442\u0438\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0439 \u0442\u043e\u0432\u0430\u0440 &#171;\u0424\u0443\u0442\u0431\u043e\u043b\u043a\u0430 Senior Developer&#187;, \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043d\u0430 \u0444\u0440\u043e\u043d\u0442\u0435 \u0432\u044b\u0431\u0440\u0430\u0442\u044c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044e \u0438 \u0437\u0430\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0434\u043b\u044f \u043d\u0435\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b, \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0430\u0440\u0442\u0438\u043a\u0443\u043b\u044b, \u0438\u0445 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0438 \u0446\u0435\u043d\u0443. \u041f\u0440\u0438 \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430 &#171;\u0446\u0432\u0435\u0442&#187; \u043c\u044b \u0445\u043e\u0442\u0438\u043c, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0434\u0430\u0432\u0435\u0446 \u043d\u0435 \u0440\u0443\u043a\u0430\u043c\u0438 \u0432\u0432\u043e\u0434\u0438\u043b \u043d\u0443\u0436\u043d\u044b\u0439 \u0435\u043c\u0443 \u0446\u0432\u0435\u0442, \u0430 \u0432\u044b\u0431\u0440\u0430\u043b \u0438\u0437 \u0441\u043f\u0438\u0441\u043a\u0430 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0445 \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0439, \u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0438 \u0442\u0430\u043a\u043e\u0432\u043e\u0433\u043e &#8212; \u0437\u0430\u043f\u043e\u043b\u043d\u0438\u043b \u043f\u043e\u043b\u0435 \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e. \u0410\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0438 \u0441 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430\u043c\u0438. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0430 \u0431\u044d\u043a\u0435.<\/p>\n<p>\u0414\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0437\u0430\u0434\u0430\u0447\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0432 \u0411\u0414. <\/p>\n<pre><code class=\"sql\">CREATE TABLE categories(     id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,     name VARCHAR(255) NOT NULL UNIQUE,     parent_id BIGINT REFERENCES categories(id),     created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,     updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP );  CREATE TABLE attributes(     id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,     composite_attribute_id BIGINT REFERENCES attributes(id),     name VARCHAR(255) NOT NULL UNIQUE,     short_name VARCHAR(255) NOT NULL,     is_faceted BOOLEAN NOT NULL DEFAULT true,     is_composite BOOLEAN NOT NULL DEFAULT false,     created_at TIMESTAMP NOT NULL,     updated_at TIMESTAMP NOT NULL,     numeric_values JSONB,     string_values JSONB,     color_values JSONB,     a_type VARCHAR(8) );  CREATE TABLE category_attributes(     category_id BIGINT REFERENCES categories(id) NOT NULL,     attribute_id BIGINT REFERENCES attributes(id) NOT NULL,     is_required BOOLEAN NOT NULL DEFAULT false,     PRIMARY KEY(category_id, attribute_id) );<\/code><\/pre>\n<p>\u0414\u043b\u044f \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u043e\u0431 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430\u0445 \u0432 \u043a\u043e\u0434\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0438\u0435\u0440\u0430\u0440\u0445\u0438\u044f, \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u0430\u044f \u0442\u043e\u0439, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u0434\u043b\u044f <code>AttributeDto<\/code>, \u043e\u0434\u043d\u0430\u043a\u043e \u0442\u0430\u043a \u043a\u0430\u043a \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f Spring Data JPA \u0438 Hibernate \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0435 persistence provider, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u0430\u0442\u044c Hibernate, \u043a\u0430\u043a\u043e\u0439 \u0442\u0438\u043f \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c. \u0412 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044d\u0442\u043e <code>InheritanceType.SINGLE_TABLE<\/code> &#8212; \u043a\u043e\u0433\u0434\u0430 \u0434\u0430\u043d\u043d\u044b\u0435 \u043e\u0431\u043e \u0432\u0441\u0435\u0445 \u043f\u043e\u0434\u0442\u0438\u043f\u0430\u0445 \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0432 \u043e\u0434\u043d\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435, \u0430 \u0441\u0430\u043c\u0438 \u043f\u043e\u0434\u0442\u0438\u043f\u044b \u0440\u0430\u0437\u043b\u0438\u0447\u0430\u044e\u0442\u0441\u044f \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c, \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u043c \u0432 <code>@DiscriminatorValue<\/code>. \u041f\u043b\u044e\u0441\u043e\u043c \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0430 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0441\u0442\u043e\u0442\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0432\u044b\u0431\u043e\u0440\u043a\u0438 \u043a\u0430\u043a \u043f\u0440\u0438 \u043f\u043e\u043b\u0438\u043c\u043e\u0440\u0444\u043d\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u0430\u0445, \u0442\u0430\u043a \u0438 \u043f\u0440\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0435 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u043f\u043e\u0434\u0442\u0438\u043f\u0430. \u041e\u0434\u043d\u0438\u043c \u0438\u0437 \u043c\u0438\u043d\u0443\u0441\u043e\u0432 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043d\u0430\u043b\u043e\u0436\u0438\u0442\u044c <code>NOT NULL<\/code> \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u0431\u0430\u0437\u044b \u043d\u0430 \u0441\u0442\u043e\u043b\u0431\u0446\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u043f\u043e\u0434\u0442\u0438\u043f\u0430\u0445. <\/p>\n<p>\u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0442\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u0441\u0430\u043c\u0438 \u043c\u043e\u0434\u0435\u043b\u0438: <\/p>\n<pre><code class=\"kotlin\">@Entity @Table(name = \"attributes\") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = \"a_type\") abstract class Attribute(     @Id     @GeneratedValue(strategy = GenerationType.IDENTITY)     var id: Long? = null,     var name: String,     @Column(name = \"short_name\", nullable = false)     var shortName: String,     @Column(name = \"is_faceted\", nullable = false)     var isFaceted: Boolean,     @ManyToOne(fetch = FetchType.LAZY)     @JoinColumn(name = \"composite_attribute_id\")     var compositeAttribute: CompositeAttribute? = null ) {      @Column(name = \"created_at\")     @org.hibernate.annotations.CreationTimestamp     var createdAt: LocalDateTime? = null      @Column(name = \"updated_at\")     @org.hibernate.annotations.UpdateTimestamp     var updatedAt: LocalDateTime? = null      override fun equals(other: Any?): Boolean {         val o = other as? Attribute ?: return false         return id != null &amp;&amp; id == o.id     }      override fun hashCode(): Int {         return javaClass.hashCode()     } }  @Entity @DiscriminatorValue(\"SA\") class StringAttribute(     name: String,     shortName: String,     isFaceted: Boolean,     @org.hibernate.annotations.Type(JsonBinaryType::class)     @Column(name = \"string_values\", columnDefinition = \"jsonb\")     var stringValues: MutableList&lt;StringAttributeValueDto> = arrayListOf() ) : Attribute(name = name, shortName = shortName, isFaceted = isFaceted)  {      override fun toString(): String {         return \"StringAttribute(id=$id, name='$name', shortName='$shortName', isFaceted=$isFaceted)\"     } }  @Entity @DiscriminatorValue(\"NA\") class NumericAttribute(     name: String,     shortName: String,     isFaceted: Boolean,     @org.hibernate.annotations.Type(JsonBinaryType::class)     @Column(name = \"numeric_values\", columnDefinition = \"jsonb\")     var numericValues: MutableList&lt;NumericAttributeValueDto> = arrayListOf() ) : Attribute(name = name, shortName = shortName, isFaceted = isFaceted) {      override fun toString(): String {         return \"NumericAttribute(id=$id, name='$name', shortName='$shortName', isFaceted=$isFaceted)\"     } }  @Entity @DiscriminatorValue(\"CLA\") class ColorAttribute(     name: String,     shortName: String,     isFaceted: Boolean,     @org.hibernate.annotations.Type(JsonBinaryType::class)     @Column(name = \"color_values\", columnDefinition = \"jsonb\")     var colorValues: MutableList&lt;ColorAttributeValueDto> = arrayListOf() ) : Attribute(name = name, shortName = shortName, isFaceted = isFaceted) {      override fun toString(): String {         return \"ColorAttribute(id=$id, name='$name', shortName='$shortName', isFaceted=$isFaceted)\"     } }  @Entity @DiscriminatorValue(\"CA\") class CompositeAttribute(     name: String,     shortName: String,     isFaceted: Boolean,     @OneToMany(mappedBy = \"compositeAttribute\", cascade = [CascadeType.PERSIST])     var subAttributes: MutableSet&lt;Attribute> = hashSetOf() ) : Attribute(name = name, shortName = shortName, isFaceted = isFaceted) {      override fun toString(): String {         return \"CompositeAttribute(id=$id, name='$name', shortName='$shortName', isFaceted=$isFaceted)\"     } }<\/code><\/pre>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b Hibernate \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043b \u043d\u0430\u0448 \u0442\u0438\u043f \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 JSONB \u0438 \u043e\u0431\u0440\u0430\u0442\u043d\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 \u043f\u0440\u043e\u0435\u043a\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c \u043d\u0430 Hypersistence Utils <code>io.hypersistence:hypersistence-utils-hibernate-63:3.7.0<\/code> <br \/>(\u0432\u0435\u0440\u0441\u0438\u044e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043a\u043e\u0440\u0435\u043b\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441 \u0432\u0435\u0440\u0441\u0438\u0435\u0439 Hibernate), \u0430 \u0442\u0430\u043a\u0436\u0435 \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0430\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u044e <code>@org.hibernate.annotations.Type(JsonBinaryType::class)<\/code> \u043d\u0430\u0434 \u0442\u0440\u0435\u0431\u0443\u0435\u043c\u044b\u043c \u043f\u043e\u043b\u0435\u043c. \u0411\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u043c\u0430\u043f\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 JSONB \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c <a href=\"https:\/\/vladmihalcea.com\/how-to-map-json-objects-using-generic-hibernate-types\/\" rel=\"noopener noreferrer nofollow\">\u0442\u0443\u0442<\/a>.<\/p>\n<p>\u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043d\u0430 \u044d\u0442\u043e\u043c \u0437\u0430\u043a\u0430\u043d\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0445, \u0438 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0440\u0430\u0431\u043e\u0442\u0430 \u0441 <a href=\"https:\/\/www.postgresql.org\/docs\/16\/functions-json.html\" rel=\"noopener noreferrer nofollow\">\u0444\u0443\u043d\u043a\u0446\u0438\u044f\u043c\u0438 Postgres \u0434\u043b\u044f JSONB<\/a>. <\/p>\n<h3>\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e jsonb_set<\/h3>\n<p>\u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u043b\u043e\u0433\u0438\u043a\u0430, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u0430\u044f \u0441 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0445 \u043e \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0435 \u0442\u043e\u0432\u0430\u0440\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>jsonb_set<\/code> \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438: <a href=\"https:\/\/github.com\/aasmc\/product-service-demo\/blob\/master\/src\/main\/kotlin\/ru\/aasmc\/productservice\/storage\/repository\/ProductVariantRepository.kt\" rel=\"noopener noreferrer nofollow\">ProductVariantRepository<\/a>. \u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e SQL \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432. \u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u043f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 Spring Data JPA \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b, \u0435\u0441\u043b\u0438 \u0445\u043e\u0442\u0438\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 <code>jsonb_set<\/code>.<\/p>\n<h4>\u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0446\u0435\u043d\u0443 \u0430\u0440\u0442\u0438\u043a\u0443\u043b\u0430<\/h4>\n<p>\u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e. \u0411\u0443\u0434\u0435\u043c \u0438\u0441\u0445\u043e\u0434\u0438\u0442\u044c \u0438\u0437 \u0437\u043d\u0430\u043d\u0438\u044f \u043e \u0442\u043e\u043c, \u0447\u0442\u043e \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 <code>product_variants<\/code> \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u043e \u0442\u043e\u0432\u0430\u0440\u0435 \u0441\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u0441\u043f\u0438\u0441\u043a\u043e\u043c \u0430\u0440\u0442\u0438\u043a\u0443\u043b\u043e\u0432:<\/p>\n<pre><code class=\"json\">{     \"attrName\": \"clothes size\",     \"skus\": [       {         \"attrValue\": \"XS\",         \"price\": 10,         \"stock\": 10,         \"sku\": \"t-shirt\/blue\/XS\/Brand\/230\"       },       {         \"attrValue\": \"S\",         \"price\": 10,         \"stock\": 10,         \"sku\": \"t-shirt\/blue\/S\/Brand\/231\"       },       {         \"attrValue\": \"M\",         \"price\": 10,         \"stock\": 10,         \"sku\": \"t-shirt\/blue\/M\/Brand\/232\"       }     ]   }<\/code><\/pre>\n<p>\u0418 \u043d\u0430\u043c \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0446\u0435\u043d\u0443 \u043d\u0443\u043b\u0435\u0432\u043e\u0433\u043e \u0430\u0440\u0442\u0438\u043a\u0443\u043b\u0430 \u0441 <code>sku=\"t-shirt\/blue\/XS\/Brand\/230\"<\/code>. \u0427\u0442\u043e\u0431\u044b \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u0435\u0439 <code>jsonb_set<\/code>. \u041d\u0438\u0436\u0435 \u043f\u0440\u0438\u0432\u0435\u0434\u0443 \u043f\u043e\u043b\u043d\u043e\u0435 \u0435\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0438\u0437 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438. <\/p>\n<pre><code>jsonb_set ( target jsonb, path text[], new_value jsonb [, create_if_missing boolean ] ) \u2192 jsonb Returns target with the item designated by path replaced by new_value, or with new_value added if create_if_missing is true (which is the default) and the item designated by path does not exist. All earlier steps in the path must exist, or the target is returned unchanged. As with the path oriented operators, negative integers that appear in the path count from the end of JSON arrays. If the last path step is an array index that is out of range, and create_if_missing is true, the new value is added at the beginning of the array if the index is negative, or at the end of the array if it is positive. jsonb_set('[{\"f1\":1,\"f2\":null},2,null,3]', '{0,f1}', '[2,3,4]', false) \u2192 [{\"f1\": [2, 3, 4], \"f2\": null}, 2, null, 3] jsonb_set('[{\"f1\":1,\"f2\":null},2]', '{0,f3}', '[2,3,4]') \u2192 [{\"f1\": 1, \"f2\": null, \"f3\": [2, 3, 4]}, 2]<\/code><\/pre>\n<p>\u0418\u0442\u0430\u043a, \u0438\u0437 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043f\u043e\u043d\u044f\u0442\u043d\u043e, \u0447\u0442\u043e \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0432 JSONB, \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043d\u0430\u0439\u0442\u0438 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0439 \u043f\u0443\u0442\u044c \u0434\u043e \u043f\u043e\u043b\u044f, \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043c\u044b \u0445\u043e\u0442\u0438\u043c \u043f\u043e\u043c\u0435\u043d\u044f\u0442\u044c. \u0412 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 &#8212; \u044d\u0442\u043e <code>'{sku,0,price}'<\/code>, \u0433\u0434\u0435 0 &#8212; \u0438\u043d\u0434\u0435\u043a\u0441 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 <code>Sku<\/code> \u0432 \u0441\u043f\u0438\u0441\u043a\u0435. \u041e\u0434\u043d\u0430\u043a\u043e \u043f\u0440\u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043d\u0430 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u0430\u043c \u043d\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044f \u0438\u043d\u0434\u0435\u043a\u0441. \u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0435\u0433\u043e \u043d\u0430\u0439\u0442\u0438, \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f CTE \u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0435\u0439 <code>jsonb_array_elements<\/code>, \u0440\u0430\u0441\u043a\u0440\u044b\u0432\u0430\u044e\u0449\u0435\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0438\u0437 JSON \u0441\u043f\u0438\u0441\u043a\u0430 \u0432\u043e \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439. \u0422\u0430\u043a\u0436\u0435 \u043d\u0430\u043c \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435 <code>WITH ORDINALITY<\/code>, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u0432 \u0432\u044b\u0445\u043e\u0434\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0441\u0442\u043e\u043b\u0431\u0435\u0446 \u0442\u0438\u043f\u0430 <code>bigint<\/code>, \u0447\u0438\u0441\u043b\u0430 \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0442\u0441\u044f \u0441 1 \u0438 \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u043d\u0430 1 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0438, \u0432\u044b\u0434\u0430\u043d\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0435\u0439. \u0422\u0443\u0442 \u0441\u0442\u043e\u0438\u0442 \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c, \u0447\u0442\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u044b \u0432 JSONB \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0442\u0441\u044f \u0441 0, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0438\u0437 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u043e\u0433\u043e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <code>WITH ORDINALITY<\/code> \u0438\u043d\u0434\u0435\u043a\u0441\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u044b\u0447\u0435\u0441\u0442\u044c \u0435\u0434\u0438\u043d\u0438\u0446\u0443. \u0422\u0430\u043a\u0436\u0435 \u0441\u0442\u043e\u0438\u0442 \u0437\u0430\u043f\u043e\u043c\u043d\u0438\u0442\u044c, \u0447\u0442\u043e <code>WITH ORDINALITY<\/code>\u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 <code>bigint<\/code>, \u0430 \u0438\u043d\u0434\u0435\u043a\u0441\u044b \u0432 \u043f\u0443\u0442\u0438  \u0434\u043b\u044f <code>jsonb_set<\/code> \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0442\u0438\u043f\u0430 <code>int<\/code>, \u0438 \u043d\u0430\u043c \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0438\u0445 \u043a\u0430\u0441\u0442\u0438\u0442\u044c. \u0422\u0430\u043a \u043a\u0430\u043a \u043f\u0443\u0442\u044c \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d \u043a \u0442\u0438\u043f\u0443 <code>text[]<\/code>, \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u043c \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441\u043e\u043c Postgres. \u041f\u0440\u0438 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u0438\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u044d\u043a\u0440\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0432\u043e\u0435\u0442\u043e\u0447\u0438\u044f \u043e\u0431\u0440\u0430\u0442\u043d\u044b\u043c \u0441\u043b\u044d\u0448\u0435\u043c.<\/p>\n<p>\u0422\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 CTE:<\/p>\n<pre><code class=\"sql\">WITH sku_path AS (     SELECT ('{skus,'||index - 1||',price}')\\:\\:text[] AS path     FROM product_variants, jsonb_array_elements(sku_collection->'skus') WITH ORDINALITY arr(sku_element, index)     WHERE id = :variantId AND sku_element->>'sku' = :sku )<\/code><\/pre>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0441\u0442\u043e\u0438\u0442 \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u043d\u044b\u0439 \u0434\u043b\u044f JSONB \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 <code>->><\/code>. \u041e\u043d \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0432 \u0432\u0438\u0434\u0435 \u0441\u0442\u0440\u043e\u043a\u0438. <\/p>\n<p>\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0434\u0435\u043b\u043e \u0437\u0430 \u043c\u0430\u043b\u044b\u043c &#8212; \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u043f\u0443\u0442\u044c \u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>jsonb_set<\/code>. \u0422\u0430\u043a \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0446\u0435\u043d\u044b \u0430\u0440\u0442\u0438\u043a\u0443\u043b\u0430:<\/p>\n<pre><code class=\"kotlin\">@Modifying @Query(   value = \"\"\"           WITH sku_path AS (               SELECT ('{skus,'||index - 1||',price}')\\:\\:text[] AS path               FROM product_variants, jsonb_array_elements(sku_collection->'skus') WITH ORDINALITY arr(sku_element, index)               WHERE id = :variantId AND sku_element->>'sku' = :sku           )           UPDATE product_variants           SET sku_collection = jsonb_set(sku_collection, sku_path.path, to_jsonb(:newPrice), false)           FROM sku_path            WHERE id = :variantId        \"\"\", nativeQuery = true ) fun updateSkuPrice(   @Param(\"sku\") sku: String,   @Param(\"variantId\") variantId: Long,   @Param(\"newPrice\") newPrice: BigDecimal ): Int<\/code><\/pre>\n<h4>\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0432 \u043a\u043e\u043d\u0435\u0446 \u0441\u043f\u0438\u0441\u043a\u0430<\/h4>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0440\u0435\u0434\u043f\u043e\u043b\u043e\u0436\u0438\u043c, \u0447\u0442\u043e \u043d\u0430\u043c \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 \u043a\u043e\u043d\u0435\u0446 \u0441\u043f\u0438\u0441\u043a\u0430 \u0432 JSONB \u043a\u0430\u043a\u043e\u0435-\u0442\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0435\u0449\u0435 \u043e\u0434\u043d\u0443 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u044e. <\/p>\n<p>\u0414\u043e\u043f\u0443\u0441\u0442\u0438\u043c, \u0441\u0435\u0439\u0447\u0430\u0441 \u0445\u0440\u0430\u043d\u0438\u0442\u044c\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u043e \u0444\u043e\u0442\u043e \u0432 image_collection:<\/p>\n<pre><code class=\"json\">{   \"images\": [     {\"url\":\"http:\/\/images.com\/old_blue_image.png\",\"isPrimary\":true}   ] }<\/code><\/pre>\n<p>\u041a\u0430\u043a \u0438 \u0432 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u0435\u0439 <code>jsonb_set<\/code>. \u0421\u0435\u0439\u0447\u0430\u0441 \u043c\u044b \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u0437\u043d\u0430\u0435\u043c \u043f\u0443\u0442\u044c \u0434\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 JSONB, \u0432 \u043a\u043e\u043d\u0435\u0446 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043d\u043e\u0432\u044b\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0430\u043c \u043d\u0435 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f CTE. \u041e\u0434\u043d\u0430\u043a\u043e \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c\u044b\u0439 \u0432 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u044e \u043a\u043b\u0430\u0441\u0441 <code>AppImage<\/code> \u0432 \u0441\u0442\u0440\u043e\u043a\u0443 \u0432 \u0432\u0438\u0434\u0435 JSON, \u0442\u0430\u043a \u043a\u0430\u043a \u0438\u043d\u0430\u0447\u0435 \u043c\u044b \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u043c \u0435\u0433\u043e \u0441\u043a\u0430\u0441\u0442\u0438\u0442\u044c \u043a \u0442\u0438\u043f\u0443 <code>jsonb<\/code>. \u0414\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0432 \u043a\u043e\u043d\u0435\u0446 \u0441\u043f\u0438\u0441\u043a\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 <code>||<\/code>. \u041f\u043e\u043b\u043d\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"kotlin\">@Modifying @Query(   \"\"\"        UPDATE product_variants        SET image_collection = jsonb_set(image_collection, '{images}', (image_collection->'images')\\:\\:jsonb || (:photo)\\:\\:jsonb, false)        WHERE id = :variantId    \"\"\", nativeQuery = true ) fun addImage(   @Param(\"variantId\") variantId: Long,   @Param(\"photo\") photo: String ): Int<\/code><\/pre>\n<h4>\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0438\u0437 \u0441\u043f\u0438\u0441\u043a\u0430<\/h4>\n<p>\u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u043d\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u0440\u043e\u0441\u0442\u043e \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438\u0437 \u0441\u043f\u0438\u0441\u043a\u0430 \u0432 JSONB. \u041e\u0434\u043d\u0430\u043a\u043e \u044d\u0442\u043e \u043d\u0435 \u0431\u0435\u0437\u0432\u044b\u0445\u043e\u0434\u043d\u0430\u044f \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u044f. \u041c\u043e\u0436\u0435\u043c \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043a\u043e\u043c\u0431\u0438\u043d\u0430\u0446\u0438\u0435\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0439: <code>jsonb_set<\/code>, <code>jsonb_array_elements<\/code> \u0438 <code>jsonb_agg<\/code>. \u041f\u0435\u0440\u0432\u044b\u0435 \u0434\u0432\u0435 \u0443\u0436\u0435 \u0437\u043d\u0430\u043a\u043e\u043c\u044b, <code>jsonb_agg<\/code> \u0430\u0433\u0433\u0440\u0435\u0433\u0438\u0440\u0443\u0435\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0435\u043c\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0432 JSON \u0441\u043f\u0438\u0441\u043e\u043a. <\/p>\n<p>\u041b\u043e\u0433\u0438\u043a\u0430 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0442\u0430\u043a\u0430\u044f: \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u043c \u043f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0443 \u0441\u043f\u0438\u0441\u043a\u0430,  \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0432 \u043d\u0435\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435 \u0443\u0434\u043e\u0432\u043b\u0435\u0442\u0432\u043e\u0440\u044f\u044e\u0442 \u0443\u0441\u043b\u043e\u0432\u0438\u044e \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f, \u0430\u0433\u0440\u0435\u0433\u0438\u0440\u0443\u0435\u043c \u0438\u0445 \u043e\u0431\u0440\u0430\u0442\u043d\u043e \u0432 \u0441\u043f\u0438\u0441\u043e\u043a. \u041d\u043e \u0442\u0443\u0442 \u0435\u0441\u0442\u044c \u0432\u0430\u0436\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442: \u0435\u0441\u043b\u0438 \u043f\u043e\u0441\u043b\u0435 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0432 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0438\u0440\u0443\u044e\u0449\u0435\u043c \u0441\u043f\u0438\u0441\u043a\u0435 \u043d\u0435 \u043e\u0441\u0442\u0430\u043d\u0435\u0442\u0441\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432, \u0442\u043e <code>jsonb_agg<\/code> \u0432\u0435\u0440\u043d\u0435\u0442 <code>NULL<\/code>, \u0430 \u043d\u0430\u043c \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0443\u0441\u0442\u043e\u0439 \u0441\u043f\u0438\u0441\u043e\u043a. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u0435\u0439 <code>COALESCE<\/code>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043f\u0435\u0440\u0432\u043e\u0435 \u043d\u0435 <code>NULL<\/code> \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435. \u041f\u0435\u0440\u0432\u044b\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 <code>jsonb_agg<\/code>, \u0430 \u0432\u0442\u043e\u0440\u044b\u043c &#8212; \u043f\u0443\u0441\u0442\u043e\u0439 \u0441\u043f\u0438\u0441\u043e\u043a. <\/p>\n<p>\u0421\u0430\u043c \u0437\u0430\u043f\u0440\u043e\u0441:<\/p>\n<pre><code class=\"kotlin\">@Modifying @Query(   \"\"\"        UPDATE product_variants        SET image_collection = jsonb_set(            image_collection,            '{images}',            COALESCE(                (SELECT jsonb_agg(elem)                 FROM jsonb_array_elements(image_collection->'images') elem                 WHERE elem->>'url' != :imageUrl),                '[]'\\:\\:jsonb            )            )        WHERE id = :variantId    \"\"\", nativeQuery = true ) fun removeImage(   @Param(\"variantId\") variantId: Long,   @Param(\"imageUrl\") imageUrl: String ): Int<\/code><\/pre>\n<h4>\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0438\u0437 \u0441\u043f\u0438\u0441\u043a\u0430 \u043f\u0440\u0438 \u0434\u0432\u043e\u0439\u043d\u043e\u0439 \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u043e\u0441\u0442\u0438<\/h4>\n<p>\u0414\u043e\u043f\u0443\u0441\u0442\u0438\u043c, \u0447\u0442\u043e \u0441\u0435\u0439\u0447\u0430\u0441 \u0443 \u0442\u043e\u0432\u0430\u0440\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u043d\u0430\u0431\u043e\u0440 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432:<\/p>\n<pre><code class=\"json\">{   \"attributes\": [     {       \"id\": \"colorAttrId\",       \"attributeName\": \"color\",       \"shortName\": \"color\",       \"isFaceted\": true,       \"type\": \"color\",       \"isRequired\": true,       \"availableValues\": [         {           \"colorValue\": \"red\",           \"colorHex\": \"FF0000\",           \"type\": \"color_type\"         }       ]     },     {       \"id\": \"dimensId\",       \"attributeName\": \"clothes dimensions\",       \"shortName\": \"dimensions\",       \"isFaceted\": true,       \"type\": \"composite\",       \"isRequired\": false,       \"subAttributes\": [         {           \"attributeName\": \"width\",           \"shortName\": \"width\",           \"isFaceted\": true,           \"type\": \"numeric\",           \"availableValues\": [             {               \"numValue\": 10.0,               \"numRuValue\": null,               \"numUnit\": \"mm\",               \"type\": \"numeric_type\"             },             {               \"numValue\": 20.0,               \"numRuValue\": null,               \"numUnit\": \"mm\",               \"type\": \"numeric_type\"             },             {               \"numValue\": 30.0,               \"numRuValue\": null,               \"numUnit\": \"mm\",               \"type\": \"numeric_type\"             }           ]         },         {           \"attributeName\": \"length\",           \"shortName\": \"length\",           \"isFaceted\": true,           \"type\": \"numeric\",           \"availableValues\": [             {               \"numValue\": 10.0,               \"numRuValue\": null,               \"numUnit\": \"mm\",               \"type\": \"numeric_type\"             },             {               \"numValue\": 20.0,               \"numRuValue\": null,               \"numUnit\": \"mm\",               \"type\": \"numeric_type\"             },             {               \"numValue\": 30.0,               \"numRuValue\": null,               \"numUnit\": \"mm\",               \"type\": \"numeric_type\"             }           ]         },         {           \"attributeName\": \"depth\",           \"shortName\": \"depth\",           \"isFaceted\": true,           \"type\": \"numeric\",           \"availableValues\": [             {               \"numValue\": 10.0,               \"numRuValue\": null,               \"numUnit\": \"mm\",               \"type\": \"numeric_type\"             },             {               \"numValue\": 20.0,               \"numRuValue\": null,               \"numUnit\": \"mm\",               \"type\": \"numeric_type\"             },             {               \"numValue\": 30.0,               \"numRuValue\": null,               \"numUnit\": \"mm\",               \"type\": \"numeric_type\"             }           ]         }       ],       \"availableValues\": []     }   ] }<\/code><\/pre>\n<p>\u0418 \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0435 &#171;clothes dimensions&#187;, \u0432 \u043f\u043e\u0434-\u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0435 &#171;width&#187; \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 &#171;numValue&#187; = 10.0. \u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c, \u043d\u0430\u043c \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f:<\/p>\n<ol>\n<li>\n<p>\u041d\u0430\u0439\u0442\u0438 \u0438\u043d\u0434\u0435\u043a\u0441 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430 \u0441 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c &#171;clothes dimensions&#187;<\/p>\n<\/li>\n<\/ol>\n<pre><code class=\"sql\">WITH attr_path AS (       SELECT CAST((index - 1) AS INTEGER) AS idx        FROM product_variants, jsonb_array_elements(attribute_collection->'attributes') WITH ORDINALITY arr(elem, index)       WHERE id = :variantId AND elem->>'attributeName' = :attrName ),<\/code><\/pre>\n<ol start=\"2\">\n<li>\n<p>\u041d\u0430\u0439\u0442\u0438 \u0438\u043d\u0434\u0435\u043a\u0441 \u043f\u043e\u0434-\u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430 \u0441 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c &#171;width&#187; \u0438 \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0443\u0442\u044c \u0434\u043e \u0441\u043f\u0438\u0441\u043a\u0430 <code>availableValues<\/code>.<\/p>\n<\/li>\n<\/ol>\n<pre><code class=\"sql\">sub_attr_path AS (     SELECT('{attributes,'||(SELECT idx FROM attr_path)||',subAttributes,'||index - 1||',availableValues}')\\:\\:text[] as path,     CAST((index - 1) AS INTEGER) AS idx     FROM product_variants, jsonb_array_elements(attribute_collection->'attributes'->(SELECT idx AS INTEGER FROM attr_path)->'subAttributes') WITH ORDINALITY arr(elem, index)     WHERE id = :variantId AND elem->>'attributeName' = :subAttrName )<\/code><\/pre>\n<ol start=\"3\">\n<li>\n<p>\u041f\u0440\u043e\u0439\u0442\u0438 \u043f\u043e \u0441\u043f\u0438\u0441\u043a\u0443 &#171;availableValues&#187; \u0438 \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432 \u043d\u0435\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0435, \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435 \u0443\u0434\u043e\u0432\u043b\u0435\u0442\u0432\u043e\u0440\u044f\u044e\u0442 \u0443\u0441\u043b\u043e\u0432\u0438\u044e \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f &#8212; \u0442\u043e \u0435\u0441\u0442\u044c \u0432\u0441\u0435, \u043d\u0435 \u0440\u0430\u0432\u043d\u044b\u0435 10.0.<\/p>\n<\/li>\n<\/ol>\n<pre><code class=\"sql\">COALESCE(          (SELECT jsonb_agg(elem)              FROM jsonb_array_elements(attribute_collection->'attributes'->ap.idx->'subAttributes'->sap.idx->'availableValues') elem              WHERE CAST(elem->>'numValue' AS DOUBLE PRECISION) != :numValue           ),          '[]'\\:\\:jsonb      )<\/code><\/pre>\n<p>\u0412 \u0446\u0435\u043b\u043e\u043c \u0437\u0430\u0434\u0430\u0447\u0430 \u043f\u043e\u0445\u043e\u0436\u0430 \u043d\u0430 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0435, \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432\u0442\u043e\u0440\u043e\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u043e\u0441\u0442\u0438. \u041e\u0431\u0449\u0438\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"kotlin\">@Modifying @Query(\"\"\"        WITH attr_path AS (            SELECT CAST((index - 1) AS INTEGER) AS idx             FROM product_variants, jsonb_array_elements(attribute_collection->'attributes') WITH ORDINALITY arr(elem, index)            WHERE id = :variantId AND elem->>'attributeName' = :attrName        ),         sub_attr_path AS (            SELECT('{attributes,'||(SELECT idx FROM attr_path)||',subAttributes,'||index - 1||',availableValues}')\\:\\:text[] as path,            CAST((index - 1) AS INTEGER) AS idx            FROM product_variants, jsonb_array_elements(attribute_collection->'attributes'->(SELECT idx AS INTEGER FROM attr_path)->'subAttributes') WITH ORDINALITY arr(elem, index)            WHERE id = :variantId AND elem->>'attributeName' = :subAttrName        )        UPDATE product_variants        SET attribute_collection = jsonb_set(            attribute_collection,            sap.path,            COALESCE(                     (SELECT jsonb_agg(elem)                         FROM jsonb_array_elements(attribute_collection->'attributes'->ap.idx->'subAttributes'->sap.idx->'availableValues') elem                         WHERE CAST(elem->>'numValue' AS DOUBLE PRECISION) != :numValue                      ),                     '[]'\\:\\:jsonb                 )        )        FROM attr_path ap, sub_attr_path sap         WHERE id = :variantId    \"\"\", nativeQuery = true) fun removeCompositeAttributeNumericValue(   @Param(\"variantId\") variantId: Long,   @Param(\"attrName\") attrName: String,   @Param(\"subAttrName\") subAttrName: String,   @Param(\"numValue\") numValue: Double,   ): Int<\/code><\/pre>\n<p>\u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043e\u0447\u0435\u043d\u044c \u043f\u043e\u0445\u043e\u0436\u0438 \u043d\u0430 \u0442\u043e, \u0447\u0442\u043e \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043d\u043e \u0432\u044b\u0448\u0435, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0435 \u0441\u043e\u0441\u0442\u0430\u0432\u044f\u0442 \u0442\u0440\u0443\u0434\u0430 \u0434\u043b\u044f \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f. \u0414\u043b\u044f \u043d\u0430\u0433\u043b\u044f\u0434\u043d\u043e\u0441\u0442\u0438 \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0435\u0441\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u043a\u0430\u043a <code>ProductVariantRepository<\/code>, \u0442\u0430\u043a \u0438 <code>ProductVariantController<\/code> &#8212; \u0437\u0430\u043f\u0443\u0441\u043a \u0442\u0435\u0441\u0442\u043e\u0432 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043d\u0430\u043b\u0438\u0447\u0438\u044f <code>Docker<\/code>, \u0442\u0430\u043a \u043a\u0430\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <code>TestContainers<\/code>. <\/p>\n<p>\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0445\u043e\u0447\u0443 \u0441\u043a\u0430\u0437\u0430\u0442\u044c, \u0447\u0442\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u0440\u0430\u0431\u043e\u0442\u044b \u0441 JSONB \u0432 Postgres \u0433\u043e\u0440\u0430\u0437\u0434\u043e \u043e\u0431\u0448\u0438\u0440\u043d\u0435\u0435 \u0442\u043e\u0433\u043e, \u0447\u0442\u043e \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u043e \u0432\u044b\u0448\u0435: \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u0434\u0440\u0443\u0433\u0438\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u0439, <a href=\"https:\/\/www.postgresql.org\/docs\/16\/functions-aggregate.html\" rel=\"noopener noreferrer nofollow\">\u0430\u0433\u0433\u0440\u0435\u0433\u0438\u0440\u0443\u044e\u0449\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u044b<\/a>, \u043c\u043e\u0436\u043d\u043e <a href=\"https:\/\/postgrespro.ru\/docs\/postgrespro\/9.5\/datatype-json\" rel=\"noopener noreferrer nofollow\">\u0441\u043e\u0437\u0434\u0430\u0442\u044c GIN \u0438\u043d\u0434\u0435\u043a\u0441<\/a> \u0441 \u043a\u043b\u0430\u0441\u0441\u043e\u043c \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u0432 <code>jsonb_ops<\/code> \u0438\u043b\u0438 <code>jsonb_path_ops,<\/code> \u043e\u0442\u043b\u0438\u0447\u0430\u044e\u0449\u0438\u0445\u0441\u044f \u043f\u043e \u043d\u0430\u0431\u043e\u0440\u0443 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u0432, \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u043f\u0430\u043c\u044f\u0442\u0438. <\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \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\/793438\/\"> https:\/\/habr.com\/ru\/articles\/793438\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442! \u041c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 \u0410\u043b\u0435\u043a\u0441\u0430\u043d\u0434\u0440, \u0432 \u0434\u0430\u043d\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u044f \u043d\u0430 \u0444\u0440\u0438\u043b\u0430\u043d\u0441\u0435 \u0437\u0430\u043d\u0438\u043c\u0430\u044e\u0441\u044c \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u043c \u043f\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e\u0433\u043e \u043c\u0430\u0440\u043a\u0435\u0442\u043f\u043b\u0435\u0439\u0441\u0430. \u0412 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0440\u0430\u0431\u043e\u0442\u044b \u043c\u044b \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0438\u0441\u044c \u0441 \u0434\u0430\u043b\u0435\u043a\u043e \u043d\u0435 \u043d\u043e\u0432\u043e\u0439 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043e\u0439 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u0442\u043e\u0432\u0430\u0440\u0430\u0445, \u0438\u043c\u0435\u044e\u0449\u0438\u0445 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0441\u0442\u0438\u043a\u0438 \u0438 \u0446\u0435\u043d\u0443, \u0437\u0430\u0432\u0438\u0441\u044f\u0449\u0443\u044e \u043e\u0442 \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0441\u0442\u0438\u043a. \u041d\u0430 \u043d\u0430\u0448\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0438\u043d\u0446\u0438\u043f CQRS: \u0437\u0430\u043f\u0438\u0441\u044c \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432 Postgres, \u0447\u0442\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0438\u0437 OpenSearch, \u0430 \u0434\u0430\u043d\u043d\u044b\u0435 \u043c\u0435\u0436\u0434\u0443 \u043d\u0438\u043c\u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u044e\u0442\u0441\u044f \u043f\u043e \u0448\u0438\u043d\u0435, \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u043d\u0430 Kafka. \u0422\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043e\u0431\u0443\u0441\u043b\u043e\u0432\u0438\u043b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0440\u0435\u043b\u044f\u0446\u0438\u043e\u043d\u043d\u043e\u0439 \u0411\u0414 \u0434\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043d\u0435\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0439 \u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438. <\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u0443\u0432\u0438\u0434\u0435\u0442\u044c, \u043f\u043e\u0447\u0435\u043c\u0443 \u044d\u0442\u0430 \u0437\u0430\u0434\u0430\u0447\u0430 \u043d\u0435 \u043d\u0430\u043a \u043f\u0440\u043e\u0441\u0442\u0430, \u043a\u0430\u043a \u043a\u0430\u0436\u0435\u0442\u0441\u044f \u0441 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0432\u0437\u0433\u043b\u044f\u0434\u0430, \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u043c, \u0447\u0442\u043e \u0432 \u043d\u0430\u0448\u0435\u043c \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0435 \u0435\u0441\u0442\u044c \u0444\u0443\u0442\u0431\u043e\u043b\u043a\u0438 \u043c\u0443\u0436\u0441\u043a\u0438\u0435 \u0432\u0441\u0435\u0433\u043e \u0441 \u0434\u0432\u0443\u043c\u044f \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430\u043c\u0438: \u0446\u0432\u0435\u0442  \u0438 \u0440\u0430\u0437\u043c\u0435\u0440. \u041c\u044b \u0445\u043e\u0442\u0438\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0442\u043e\u0432\u0430\u0440 \u0441 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c &#171;\u0424\u0443\u0442\u0431\u043e\u043b\u043a\u0430 Junior Developer&#187;, \u043e\u043d\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 \u0441\u0438\u043d\u0435\u043c, \u043a\u0440\u0430\u0441\u043d\u043e\u043c \u0438 \u0437\u0435\u043b\u0435\u043d\u043e\u043c \u0446\u0432\u0435\u0442\u0430\u0445, \u0438 \u043a\u0430\u0436\u0434\u044b\u0439 \u0446\u0432\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0432 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0440\u0430\u0437\u043c\u0435\u0440\u0430\u0445. \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u044f \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438, \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u043c, \u0447\u0442\u043e \u0446\u0435\u043d\u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0439 \u0444\u0443\u0442\u0431\u043e\u043b\u043a\u0438 \u0442\u0430\u043a\u0436\u0435 \u0432\u0430\u0440\u044c\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0446\u0432\u0435\u0442\u0430 \u0438 \u0440\u0430\u0437\u043c\u0435\u0440\u0430. \u041a\u0430\u043a \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u044d\u0442\u0443 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c \u0432 \u0440\u0435\u043b\u044f\u0446\u0438\u043e\u043d\u043d\u043e\u0439 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445, \u0441 \u0443\u0447\u0435\u0442\u043e\u043c \u0442\u043e\u0433\u043e, \u0447\u0442\u043e \u043f\u0440\u043e\u0434\u0430\u0432\u0435\u0446 \u0444\u0443\u0442\u0431\u043e\u043b\u043a\u0438 \u043c\u043e\u0436\u0435\u0442 \u0432 \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u043c\u043e\u043c\u0435\u043d\u0442 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b \u0434\u043b\u044f \u0441\u0432\u043e\u0435\u0433\u043e \u0442\u043e\u0432\u0430\u0440\u0430, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0433\u0430\u0431\u0430\u0440\u0438\u0442\u044b \u0443\u043f\u0430\u043a\u043e\u0432\u043a\u0438 \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438<br \/>(\u0434\u043b\u0438\u043d\u0430, \u0448\u0438\u0440\u0438\u043d\u0430, \u0432\u044b\u0441\u043e\u0442\u0430)? <\/p>\n<p>\u0421 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u044b, \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434: <a href=\"https:\/\/en.wikipedia.org\/wiki\/Entity%E2%80%93attribute%E2%80%93value_model\" rel=\"noopener noreferrer nofollow\">Entity-Attribute-Value<\/a>. \u041e\u043d \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0433\u0438\u0431\u043a\u043e \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c \u0441\u0432\u044f\u0437\u0438 \u043c\u0435\u0436\u0434\u0443 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044f\u043c\u0438, \u0438\u0445 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430\u043c\u0438 \u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u043c\u0438, \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043d\u043e\u0432\u044b\u0445 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438. \u041e\u0434\u043d\u0430\u043a\u043e \u0443 \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0430 \u0435\u0441\u0442\u044c \u0441\u0432\u043e\u0438 \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043a\u0438. \u041d\u0430 \u043d\u0438\u0445 \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0435 \u0431\u0443\u0434\u0443 &#8212; \u0432 \u0441\u0442\u0430\u0442\u044c\u0435: <a href=\"https:\/\/habr.com\/ru\/articles\/475178\/\" rel=\"noopener noreferrer nofollow\">&#171;\u0417\u0430\u043c\u0435\u043d\u0430 EAV \u043d\u0430 JSONB \u0432 PostgreSQL&#187;<\/a> \u043e\u043d\u0438 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u044b, \u0442\u0430\u043a\u0436\u0435 \u0442\u0430\u043c \u0435\u0441\u0442\u044c \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u0435 \u043f\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u043f\u0430\u043c\u044f\u0442\u0438 EAV \u0438 JSONB. <\/p>\n<p>\u042f \u0436\u0435 \u043f\u043e\u0441\u0442\u0430\u0440\u0430\u044e\u0441\u044c \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043d\u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435, \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u0438\u043f \u0434\u0430\u043d\u043d\u044b\u0445 JSONB \u0434\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0439, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0441\u043e\u043f\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u0437\u0430\u0434\u0430\u0447 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432 \u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u0439 Postgres \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 JSONB. \u041a\u0430\u043a \u0438 \u0432 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0445 \u043f\u043e\u0441\u0442\u0430\u0445, \u044f \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043b \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 <a href=\"https:\/\/github.com\/aasmc\/product-service-demo\" rel=\"noopener noreferrer nofollow\">\u0434\u0435\u043c\u043e \u043f\u0440\u043e\u0435\u043a\u0442<\/a>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u044b\u043b\u043e\u0436\u0438\u043b \u043d\u0430 Github. \u042f \u043d\u0435 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u044e, \u0447\u0442\u043e \u0434\u0430\u043d\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043e\u043f\u0442\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u043c, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0431\u0443\u0434\u0443 \u0440\u0430\u0434 \u043b\u044e\u0431\u043e\u0439 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u0438\u0432\u043d\u043e\u0439 \u043a\u0440\u0438\u0442\u0438\u043a\u0435. \u0418\u0442\u0430\u043a, \u043f\u0440\u0438\u0441\u0442\u0443\u043f\u0438\u043c.<\/p>\n<h2>\u041c\u043e\u0434\u0435\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0445<\/h2>\n<h4>Product \u0438 ProductVariant. <\/h4>\n<p>\u041e\u0431\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0442\u043e\u0432\u0430\u0440\u0435 \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 products:<\/p>\n<pre><code class=\"sql\">CREATE TABLE products(     id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,     shop_id BIGINT REFERENCES shops(id),     category_id BIGINT REFERENCES categories(id),     name TEXT NOT NULL,     description TEXT NOT NULL,     created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,     updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP );<\/code><\/pre>\n<p>\u0414\u0435\u0442\u0430\u043b\u0438 \u043e \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430\u0445 \u0442\u043e\u0432\u0430\u0440\u0430, \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c\u044b\u0445 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u043a\u043e\u043c\u0431\u0438\u043d\u0430\u0446\u0438\u0439 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432,  \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 product_variants:<\/p>\n<pre><code class=\"sql\">CREATE TABLE product_variants (     id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,     variant_name TEXT NOT NULL,     product_id BIGINT REFERENCES products(id) ON DELETE CASCADE,     attribute_collection JSONB,     image_collection JSONB,     sku_collection JSONB,     price DECIMAL NOT NULL,     created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,     updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP );<\/code><\/pre>\n<p>\u0414\u043b\u044f \u043d\u0430\u0441 \u0438\u043d\u0442\u0435\u0440\u0435\u0441 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442 \u0442\u0440\u0438 \u043f\u043e\u043b\u044f:<\/p>\n<ul>\n<li>\n<p>image_collection<\/p>\n<\/li>\n<li>\n<p>attribute_collection<\/p>\n<\/li>\n<li>\n<p>sku_collection<\/p>\n<\/li>\n<\/ul>\n<h4>image_collection<\/h4>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0442\u043e\u0432\u0430\u0440\u0430 \u043c\u043e\u0436\u0435\u0442 \u0438\u043c\u0435\u0442\u044c \u043e\u0434\u043d\u0443 \u0438\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043c\u044b \u0445\u0440\u0430\u043d\u0438\u043c \u0442\u043e\u043b\u044c\u043a\u043e url \u043d\u0430 \u0444\u043e\u0442\u043e, \u0438 \u0444\u043b\u0430\u0433, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043b\u0438 \u044d\u0442\u043e \u0444\u043e\u0442\u043e \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u043c \u0432 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438. <code>image_collection<\/code> \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043a\u043b\u0430\u0441\u0441\u043e\u043c:<\/p>\n<pre><code class=\"kotlin\">data class AppImage(     val url: String,     val isPrimary: Boolean = false )  data class ImageCollection(     val images: MutableSet&lt;AppImage> = hashSetOf() )<\/code><\/pre>\n<h4>attribute_collection<\/h4>\n<p>\u042d\u0442\u043e \u0431\u043e\u043b\u0435\u0435 \u0441\u043b\u043e\u0436\u043d\u044b\u0439 \u0441\u043b\u0443\u0447\u0430\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e. \u0410\u0442\u0440\u0438\u0431\u0443\u0442\u044b \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u044b \u043d\u0430 \u0442\u0438\u043f\u044b: \u0441\u0442\u0440\u043e\u043a\u043e\u0432\u044b\u0439, \u0447\u0438\u0441\u043b\u043e\u0432\u043e\u0439, \u0446\u0432\u0435\u0442\u043e\u0432\u043e\u0439 \u0438 \u0441\u043e\u0441\u0442\u0430\u0432\u043d\u043e\u0439. \u0417\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432 \u0442\u0430\u043a\u0436\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u044f\u044e\u0442\u0441\u044f \u043f\u043e \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u044b\u043c \u0442\u0438\u043f\u0430\u043c, \u043a\u0440\u043e\u043c\u0435 \u0441\u043e\u0441\u0442\u0430\u0432\u043d\u043e\u0433\u043e. \u0412 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0435, \u0432 \u043f\u043e\u043b\u0435 JSONB \u043c\u043e\u0436\u043d\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c <code>Map&lt;String, Any><\/code>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0438 \u0431\u0443\u0434\u0435\u0442 \u0442\u0435\u043c JSON \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u043c, \u043f\u0440\u0438\u0445\u043e\u0434\u044f\u0449\u0438\u043c \u0441 \u0444\u0440\u043e\u043d\u0442\u0430 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0442\u043e\u0432\u0430\u0440\u0430. \u041e\u0434\u043d\u0430\u043a\u043e \u0442\u0430\u043a\u043e\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u043b\u0438\u0448\u0430\u0435\u0442 \u043d\u0430\u0441 \u0441\u0442\u0440\u043e\u0433\u043e\u0439 \u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u0447\u0440\u0435\u0432\u0430\u0442 \u043e\u0448\u0438\u0431\u043a\u0430\u043c\u0438. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 Jackson \u043f\u043e \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0434\u0435\u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438\u0435\u0440\u0430\u0440\u0445\u0438\u0438 \u043a\u043b\u0430\u0441\u0441\u043e\u0432, \u0432 \u0447\u0430\u0441\u0442\u043d\u043e\u0441\u0442\u0438 \u0430\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u044f\u043c\u0438 <code>@JsonTypeInfo<\/code> \u0438 <code>@JsonSubTypes<\/code>, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0449\u0438\u043c\u0438\u0441\u044f \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u0430\u0442\u044c Jackson, \u0441 \u043a\u0430\u043a\u0438\u043c \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u043a\u043b\u0430\u0441\u0441\u043e\u043c \u0441\u0435\u0439\u0447\u0430\u0441 \u0438\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430. \u041f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c <a href=\"https:\/\/www.baeldung.com\/jackson-inheritance\" rel=\"noopener noreferrer nofollow\">\u0442\u0443\u0442<\/a>.<\/p>\n<p>\u0418\u0442\u0430\u043a, \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432 \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u0441\u043b\u0435\u0434\u0443\u0431\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"kotlin\">enum class AttributeValueType(     @field:JsonValue     val value: String ) {     STRING_TYPE(\"string_type\"),     NUMERIC_TYPE(\"numeric_type\"),     COLOR_TYPE(\"color_type\") }  @JsonTypeInfo(     use = JsonTypeInfo.Id.NAME,     visible = true,     include = JsonTypeInfo.As.EXISTING_PROPERTY,     property = \"type\" ) @JsonSubTypes(     *arrayOf(         JsonSubTypes.Type(             value = StringAttributeValueDto::class,             name = \"string_type\"         ),         JsonSubTypes.Type(             value = NumericAttributeValueDto::class,             name = \"numeric_type\"         ),         JsonSubTypes.Type(             value = ColorAttributeValueDto::class,             name = \"color_type\"         )     ) ) sealed class AttributeValueDto(     open val type: AttributeValueType )  data class StringAttributeValueDto(     val stringValue: String,     val stringRuValue: String?,     override val type: AttributeValueType = AttributeValueType.STRING_TYPE ) : AttributeValueDto(type)  data class NumericAttributeValueDto(     val numValue: Double,     val numRuValue: Double?,     val numUnit: String,     override val type: AttributeValueType = AttributeValueType.NUMERIC_TYPE ) : AttributeValueDto(type)  data class ColorAttributeValueDto(     val colorValue: String,     val colorHex: String,     override val type: AttributeValueType = AttributeValueType.COLOR_TYPE ) : AttributeValueDto(type)<\/code><\/pre>\n<p>\u0421\u0430\u043c\u0438 \u0436\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u044b \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0438\u0435\u0440\u0430\u0440\u0445\u0438\u0435\u0439 \u043a\u043b\u0430\u0441\u0441\u043e\u0432, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043a\u043b\u044e\u0447\u0435\u0432\u043e\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0435 \u0442\u0438\u043f\u043e\u0432 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432 \u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439:<\/p>\n<pre><code class=\"kotlin\">enum class AttributeType(     @field:JsonValue     val value: String ) {     STRING_ATTR(\"string\"),     NUMERIC_ATTR(\"numeric\"),     COLOR_ATTR(\"color\"),     COMPOSITE_ATTR(\"composite\") }  @JsonTypeInfo(     use = JsonTypeInfo.Id.NAME,     include = JsonTypeInfo.As.EXISTING_PROPERTY,     property = \"type\",     visible = true ) @JsonSubTypes(*arrayOf(     JsonSubTypes.Type(         value = StringAttributeDto::class,         name = \"string\"     ),     JsonSubTypes.Type(         value = NumericAttributeDto::class,         name = \"numeric\"     ),     JsonSubTypes.Type(         value = ColorAttributeDto::class,         name = \"color\"     ),     JsonSubTypes.Type(         value = CompositeAttributeDto::class,         name = \"composite\"     ) )) sealed class AttributeDto(     open val id: String?,     open val attributeName: String,     open val shortName: String,     open val isFaceted: Boolean,     open val type: AttributeType,     open val createdAt: LocalDateTime?,     open val isRequired: Boolean? = null,     open val availableValues: List&lt;AttributeValueDto> )  data class StringAttributeDto(     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val id: String? = null,     override val attributeName: String,     override val shortName: String,     override val isFaceted: Boolean,     override val type: AttributeType = AttributeType.STRING_ATTR,     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val isRequired: Boolean? = null,     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val createdAt: LocalDateTime? = null,     override val availableValues: MutableList&lt;StringAttributeValueDto>, ) : AttributeDto(id, attributeName, shortName, isFaceted, type, createdAt, isRequired, availableValues)  data class NumericAttributeDto(     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val id: String? = null,     override val attributeName: String,     override val shortName: String,     override val isFaceted: Boolean,     override val type: AttributeType = AttributeType.NUMERIC_ATTR,     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val isRequired: Boolean? = null,     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val createdAt: LocalDateTime? = null,     override val availableValues: MutableList&lt;NumericAttributeValueDto>, ) : AttributeDto(id, attributeName, shortName, isFaceted, type, createdAt, isRequired, availableValues)  data class ColorAttributeDto(     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val id: String? = null,     override val attributeName: String,     override val shortName: String,     override val isFaceted: Boolean,     override val type: AttributeType = AttributeType.COLOR_ATTR,     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val isRequired: Boolean? = null,     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val createdAt: LocalDateTime? = null,     override val availableValues: MutableList&lt;ColorAttributeValueDto>, ) : AttributeDto(id, attributeName, shortName, isFaceted, type, createdAt, isRequired, availableValues)  data class CompositeAttributeDto(     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val id: String? = null,     override val attributeName: String,     override val shortName: String,     override val isFaceted: Boolean,     override val type: AttributeType = AttributeType.COMPOSITE_ATTR,     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val isRequired: Boolean? = null,     @field:JsonInclude(JsonInclude.Include.NON_NULL)     override val createdAt: LocalDateTime? = null,     val subAttributes: List&lt;AttributeDto> ): AttributeDto(id, attributeName, shortName, isFaceted, type, createdAt, isRequired, listOf())<\/code><\/pre>\n<p>\u041e\u0441\u043e\u0431\u043d\u044f\u043a\u043e\u043c \u0441\u0442\u043e\u0438\u0442 \u0441\u043e\u0441\u0442\u0430\u0432\u043d\u043e\u0439 \u0442\u0438\u043f \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0430\u0442\u0440\u0438\u0431\u0443\u0442 &#171;\u0413\u0430\u0431\u0430\u0440\u0438\u0442\u044b&#187;, \u043c\u043e\u0436\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0432 \u0441\u0435\u0431\u0435 \u0442\u0430\u043a\u0438\u0435 \u043f\u043e\u0434-\u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b, \u043a\u0430\u043a: &#171;\u0414\u043b\u0438\u043d\u0430&#187;, &#171;\u0428\u0438\u0440\u0438\u043d\u0430&#187; \u0438 &#171;\u0412\u044b\u0441\u043e\u0442\u0430&#187;. \u0421\u0430\u043c \u0441\u043e\u0441\u0442\u0430\u0432\u043d\u043e\u0439 \u0430\u0442\u0440\u0438\u0431\u0443\u0442 \u043d\u0435 \u0438\u043c\u0435\u0435\u0442 \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0433\u043e \u0441\u043f\u0438\u0441\u043a\u0430 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439, \u0442\u0430\u043a \u043a\u0430\u043a \u0432\u0441\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0442 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u043c \u043f\u043e\u0434-\u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430\u043c. <\/p>\n<h4>sku_collection<\/h4>\n<p>\u041d\u0435\u043c\u0430\u043b\u043e\u0432\u0430\u0436\u043d\u043e\u0439<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-368096","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/368096","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=368096"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/368096\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=368096"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=368096"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=368096"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}