{"id":24666,"date":"2023-10-12T05:00:00","date_gmt":"2023-10-12T03:00:00","guid":{"rendered":"https:\/\/sii.pl\/blog\/?p=24666"},"modified":"2024-07-22T14:28:36","modified_gmt":"2024-07-22T12:28:36","slug":"abstract-crud-from-repository-to-controller-what-else-can-be-done-using-spring-generics","status":"publish","type":"post","link":"https:\/\/sii.pl\/blog\/en\/abstract-crud-from-repository-to-controller-what-else-can-be-done-using-spring-generics\/","title":{"rendered":"Abstract CRUD from Repository to Controller: What else can be done using Spring + Generics"},"content":{"rendered":"\n<p>Not long ago, an article on the Internet caught my attention. It described an intriguing approach to combining Generics and Spring capabilities. It reminded me of the method that I tested in my private programming experience. I want to present it here and show how we can use the Java Generics mechanism for building an abstract architecture.<\/p>\n\n\n\n<p>As a result, we can obtain a system where adding a new entity requires just initializing one bean in each part of the repository-service-controller trio. I assembled the project using Spring Initializr, adding JPA, Web, and H2 frameworks with Gradle and Spring Boot 3.1.3.<\/p>\n\n\n\n<p>To begin with, <strong>let&#8217;s examine the classic scenario of transferring data between the controller and the repository without any additional logic.<\/strong> If you are eager to dive into the core of this approach, feel free to scroll down to the abstract version. However, I still recommend reading the entire article.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Classic Version<\/strong><\/h2>\n\n\n\n<p>We will not be distracted by essential matters like validation, DTO mapping, and other details. Let\u2019s concentrate solely on the differences between classic and abstract approaches to constructing all layers of the application.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Domain<\/strong><\/h3>\n\n\n\n<p>This section defines the User class, which represents a domain entity. It is annotated with @Entity, indicating that it is a JPA entity.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Entity\npublic class User implements Serializable {\n\n    private Long id;\n    private String name;\n    private String phone;\n\n   \/\/getters, setters, equals, hashcode, toString\n}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>Repository<\/strong><\/h3>\n\n\n\n<p>Here, the UserRepository interface is defined, extending the CrudRepository interface for the User entity.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Repository\npublic interface UserRepository extends CrudRepository&lt;User, Long&gt; {\n}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>Service Interface<\/strong><\/h3>\n\n\n\n<p>The UserService interface is introduced, defining a contract for working with user-related operations.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\npublic interface UserService {\n\n    Optional&lt;User&gt; save(User user);\n}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>Service<\/strong><\/h3>\n\n\n\n<p>The UserServiceImpl class implements the UserService interface. It is annotated with @Service, indicating that it is a Spring service component.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Service\npublic class UserServiceImpl implements UserService {\n\n    private final UserRepository userRepository;\n\n    @Autowired\n    public UserServiceImpl(UserRepository userRepository) {\n        this.userRepository = userRepository;\n    }\n\n    @Override\n    public Optional&lt;User&gt; save(User user) {\n        return Optional.of(userRepository.save(user));\n    }\n}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>Controller<\/strong><\/h3>\n\n\n\n<p>In this section, the UserController class is defined as a Spring REST controller. It is responsible for handling HTTP requests related to users. The class has a constructor that takes a UserService as a parameter, and it uses dependency injection to initialize it.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@RestController\n@RequestMapping(&quot;\/user&quot;)\npublic class UserController {\n\n    private final UserService service;\n\n    @Autowired\n    public UserController(UserService service) {\n        this.service = service;\n    }\n\n    @PostMapping\n    public ResponseEntity&lt;User&gt; save(@RequestBody User user) {\n        return service.save(user).map(u -&gt; new ResponseEntity&lt;&gt;(u, HttpStatus.OK)).orElseThrow(() -&gt; new UserException(\nString.format(ErrorType.USER_NOT_SAVED.getDescription(), user.toString())));\n    }\n}\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\"><strong>Adding another entity<\/strong><\/h2>\n\n\n\n<p>At the end, we have a set of dependent classes that will assist us in operating the User entity at the CRUD level. Let&#8217;s say we need to add another entity, for example, Car. We will not map them to each other at the entity level (if you wish, you can create mappings). To begin, let&#8217;s create the entity.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Entity\npublic class Car implements Serializable {\n\n    private Long id;\n    private String brand;\n    private String model;\n\n    @Id\n    @GeneratedValue\n    public Long getId() {\n        return id;\n    }\n\n    public void setId(Long id) {\n        this.id = id;\n    }\n\/\/getters, setters, equals, hashcode, toString\n}\n<\/pre><\/div>\n\n\n<p>Then, we need to create the repository.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\npublic interface CarRepository extends CrudRepository&amp;lt;Car, Long&gt; {}\n<\/pre><\/div>\n\n\n<p>Next, the service.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\npublic interface CarService {\n\n    Optional&lt;Car&gt; save(Car car);\n\n    List&lt;Car&gt; saveAll(List&lt;Car&gt; cars);\n\n    Optional&lt;Car&gt; update(Car car);\n\n    Optional&lt;Car&gt; get(Long id);\n\n    List&lt;Car&gt; getAll();\n\n    Boolean deleteById(Long id);\n\n    Boolean deleteAll();\n}\n<\/pre><\/div>\n\n\n<p>Then, the service implementation, Controller, etc.Yes, you can simply copy and paste the same methods (since they are universal) from the User class, then replace &#8220;User&#8221; with &#8220;Car&#8221;, and repeat the same process for the implementation and the controller. Then, another entity is in the queue, and more and more keep appearing.<\/p>\n\n\n\n<p>Usually, one can become exhausted even after the second entity. Creating a utility architecture for a couple dozen entities (copy-pasting, replacing entity names, making mistakes here and there) leads to struggles caused by any monotonous work. Try to write twenty entities on your free time, and you&#8217;ll understand what I mean.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Abstractions based on type parameters<\/strong><\/h2>\n\n\n\n<p>At one point, while delving into generics and type parameters, it dawned on me that this process could be approached differently, providing us with the opportunity to write a smaller amount of code.<\/p>\n\n\n\n<p>Abstractions based on type parameters \u2014 the essence of this strategy is to extract all the logic into an abstraction, bind that abstraction to type parameters of an interface, and inject other beans into it. That\u2019s it. No logic in the beans themselves \u2014 only the injection of other beans. This approach involves writing the architecture and logic once, eliminating duplication when adding new entities. <\/p>\n\n\n\n<p>Let&#8217;s begin with the cornerstone of our abstraction: the abstract entity. From this entity, the chain of abstract dependencies will commence. All entities share at least one common field, usually more: the ID. We will extract this field into a separate abstract entity and inherit it for both User and Car.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>AbstractEntity<\/strong><\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@MappedSuperclass\npublic abstract class AbstractEntity implements Serializable {\n\n    private Long id;\n\n    @Id\n    @GeneratedValue\n    public Long getId() {\n        return id;\n    }\n\n    public void setId(Long id) {\n        this.id = id;\n    }\n}\n<\/pre><\/div>\n\n\n<p>Do not forget to annotate the abstraction with @MappedSuperclass. Hibernate should also recognize it as an abstraction.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>User<\/strong><\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Entity\npublic class User extends AbstractEntity {\n\n    private String name;\n    private String phone;\n   \n    \/\/...\n}\n<\/pre><\/div>\n\n\n<p>With Car, the same approach applies accordingly.<\/p>\n\n\n\n<p>In each layer, apart from the beans, we will have one interface with type parameters and one abstract class with logic, except for the repository &#8211; thanks to the specificity of Spring Data JPA, things will be much simpler here.The first thing we will need in the repository is a common repository.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>CommonRepository<\/strong><\/h2>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@NoRepositoryBean\npublic interface CommonRepository&lt;E extends AbstractEntity&gt; extends CrudRepository&lt;E, Long&gt; {}\n<\/pre><\/div>\n\n\n<p>In this repository, we establish common rules for the entire chain: all entities participating in it will inherit from the abstract one. We use the @NoRepositoryBean annotation here to indicate that we do not want Spring to create an instance of this repository. Therefore, a Bean of this repo will not be created during the context creation.<\/p>\n\n\n\n<p>Next, for each entity, we need to write its own repository interface where we specify the exact entity with which this repository-service-controller chain will work.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>UserRepository<\/strong><\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Repository\npublic interface UserRepository extends CommonRepository&lt;User&gt; {}\n<\/pre><\/div>\n\n\n<p>With this, thanks to the features of Spring Data JPA, the repository configuration is now complete, and everything will work as expected. Next comes the service. We need to create a common interface, abstraction, and bean.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>CommonService<\/strong><\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\npublic interface CommonService&lt;E extends AbstractEntity&gt; {\n    Optional&lt;E&gt; save(E entity);\n\/\/... other methods\n}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>AbstractService<\/strong><\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\npublic abstract class AbstractService&lt;E extends AbstractEntity, R extends CommonRepository&lt;E&gt;&gt; implements CommonService&lt;E&gt; {\n    protected final R repository;\n\n    @Autowired\n    public AbstractService(R repository) {\n        this.repository = repository;  }\n\/\/\u2026 other methods overridden from interface }\n<\/pre><\/div>\n\n\n<p>In this step, we override all methods and also create a parameterized constructor for the future repository, which we will override in the bean. In this way, we are already using a repository that we have not defined yet. We do not know which entity will be processed in this abstraction and which repository we will need.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>UserService<\/strong><\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Service\npublic class UserService extends AbstractService&lt;User, UserRepository&gt; {\n\n    public UserService(UserRepository repository) {\n        super(repository);\n    }\n}\n<\/pre><\/div>\n\n\n<p>In the bean, we perform the final step \u2014 explicitly defining the necessary repository that will be called in the constructor of the abstraction. And that&#8217;s it. <\/p>\n\n\n\n<p>Using the interface and abstraction, we have created a mainline through which all entities will pass. In the bean, we create the branching point from which we will connect the desired entity to the mainline.The controller follows a similar principle: we create an interface, abstraction, and bean for it.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>CommonController<\/strong><\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\npublic interface CommonController&lt;E extends AbstractEntity&gt; {\n\n    @PostMapping\n    ResponseEntity&lt;E&gt; save(@RequestBody E entity);\n\n\/\/ other methods }\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>AbstractController<\/strong><\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\npublic abstract class AbstractController&lt;E extends AbstractEntity, S extends CommonService&lt;E&gt;&gt;\n        implements CommonController&lt;E&gt; {\n\n    private final S service;\n\n    @Autowired\n    protected AbstractController(S service) {\n        this.service = service;\n    }\n\n    @Override\n    public ResponseEntity&lt;E&gt; save(@RequestBody E entity) {\n        return service.save(entity).map(ResponseEntity::ok)\n                .orElseThrow(() -&gt; new SampleException(\n                        String.format(ErrorType.ENTITY_NOT_SAVED.getDescription(), entity.toString())\n                ));\n    }\n\n\/\/ others methods\n}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>UserController<\/strong><\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@RestController\n@RequestMapping(&quot;\/user&quot;)\npublic class UserController extends AbstractController&lt;User, UserService&gt; {\n\n    public UserController(UserService service) {\n        super(service);\n    }\n}\n<\/pre><\/div>\n\n\n<p>And that\u2019s it: this is the entire structure. What&#8217;s next?<\/p>\n\n\n\n<p>Now, let&#8217;s imagine that we have a new entity. We have already inherited it from AbstractEntity, and we need to establish the same chain for it. This will take us a minute. No copy-pasting or corrections required. Let&#8217;s consider the Car entity, which is already inherited from AbstractEntity.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>CarRepository<\/strong><\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Repository\npublic interface CarRepository extends CommonRepository&lt;Car&gt; {}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>CarService<\/strong><\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Service\npublic class CarService extends AbstractService&lt;Car, CarRepository&gt; {\n    public CarService(CarRepository repository) {\n        super(repository); }}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>CarController<\/strong><\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@RestController\n@RequestMapping(&quot;\/car&quot;)\npublic class CarController extends AbstractController&lt;Car, CarService&gt; {\n\n    public CarController(CarService service) {\n        super(service);\n    }\n}\n<\/pre><\/div>\n\n\n<p>As we can see, duplicating the same logic involves simply adding a bean. There&#8217;s no need to rewrite the logic for each bean with parameter changes and signatures. These aspects are written once and work in every subsequent case.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Conclusion<\/strong><\/h2>\n\n\n\n<p>Of course, the example described is a kind of situation in vacuum where CRUD for each entity share the same logic. Such situations rarely occur \u2014 there will still be a need to override some methods in the bean or add new ones based on specific entity processing requirements. It&#8217;s good if around 50% of the total CRUD methods remain in the abstraction. This would be beneficial because the more unnecessary code we manually generate, the more time we spend on monotonous work, and the higher the risk of errors or typos.<\/p>\n\n\n\n<p>However, it&#8217;s important to acknowledge that this strategy can lead to code that may appear cumbersome and unreadable in enterprise-level projects. Therefore, I can recommend this strategy only for developing some smaller projects. It allows you to experience its pros and cons of this strategy and consider how it might be enhanced and made more suitable for larger, more complex projects.<\/p>\n\n\n\n<p>I hope you found this article helpful and interesting. Thank you for your attention. You can find the examples from this article on <a href=\"https:\/\/github.com\/turchan\/abstractCRUD\" target=\"_blank\" aria-label=\" (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >the public repository on GitHub<\/a>.<\/p>\n\n\n\n<p>***<\/p>\n\n\n\n<p>If you&#8217;re curious about spring, also take a peek at <a href=\"https:\/\/sii.pl\/blog\/wyszukiwarka\/spring\/\" target=\"_blank\" aria-label=\"other articles by our experts (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\">other articles by our experts<\/a>.<\/p>\n\n\n<div class=\"kk-star-ratings kksr-auto kksr-align-left kksr-valign-bottom\"\n    data-payload='{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;24666&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;bottom&quot;,&quot;ignore&quot;:&quot;&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;class&quot;:&quot;&quot;,&quot;count&quot;:&quot;4&quot;,&quot;legendonly&quot;:&quot;&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;5&quot;,&quot;starsonly&quot;:&quot;&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;11&quot;,&quot;greet&quot;:&quot;&quot;,&quot;legend&quot;:&quot;5\\\/5 ( votes: 4)&quot;,&quot;size&quot;:&quot;18&quot;,&quot;title&quot;:&quot;Abstract CRUD from Repository to Controller: What else can be done using Spring + Generics&quot;,&quot;width&quot;:&quot;139.5&quot;,&quot;_legend&quot;:&quot;{score}\\\/{best} ( {votes}: {count})&quot;,&quot;font_factor&quot;:&quot;1.25&quot;}'>\n            \n<div class=\"kksr-stars\">\n    \n<div class=\"kksr-stars-inactive\">\n            <div class=\"kksr-star\" data-star=\"1\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"2\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"3\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"4\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"5\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n    \n<div class=\"kksr-stars-active\" style=\"width: 139.5px;\">\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n<\/div>\n                \n\n<div class=\"kksr-legend\" style=\"font-size: 14.4px;\">\n            5\/5 ( votes: 4)    <\/div>\n    <\/div>\n","protected":false},"excerpt":{"rendered":"<p>Not long ago, an article on the Internet caught my attention. It described an intriguing approach to combining Generics and &hellip; <a class=\"continued-btn\" href=\"https:\/\/sii.pl\/blog\/en\/abstract-crud-from-repository-to-controller-what-else-can-be-done-using-spring-generics\/\">Continued<\/a><\/p>\n","protected":false},"author":570,"featured_media":24676,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_editorskit_title_hidden":false,"_editorskit_reading_time":0,"_editorskit_is_block_options_detached":false,"_editorskit_block_options_position":"{}","inline_featured_image":false,"footnotes":""},"categories":[1320],"tags":[2622,1811,1809,1808,1718],"class_list":["post-24666","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-hard-development","tag-digital-en","tag-java-generics","tag-spring-2","tag-crud","tag-repository-en"],"acf":[],"aioseo_notices":[],"republish_history":[],"featured_media_url":"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/10\/Abstract-CRUD-from-Repository-to-Controller-What-else-can-be-done-using-Spring-and-Generics.png","category_names":["Hard development"],"_links":{"self":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts\/24666"}],"collection":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/users\/570"}],"replies":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/comments?post=24666"}],"version-history":[{"count":3,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts\/24666\/revisions"}],"predecessor-version":[{"id":24675,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts\/24666\/revisions\/24675"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/media\/24676"}],"wp:attachment":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/media?parent=24666"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/categories?post=24666"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/tags?post=24666"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}