[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"work:legacy-transition:en":3},[4,20],{"type":5,"items":6},"toc",[7,11,14,17],{"id":8,"level":9,"title":10},"external-users-may-not-welcome-change",2,"External Users May Not Welcome Change",{"id":12,"level":9,"title":13},"pursuing-both-goals","Pursuing Both Goals",{"id":15,"level":9,"title":16},"api-documentation","API Documentation",{"id":18,"level":9,"title":19},"closing-thoughts","Closing Thoughts",{"type":21,"html":22},"html","\u003Cp>Software products have a lifespan. The external OpenAPI service I was responsible for was no exception. In this article, I want to write about what I learned while moving a legacy system to a new, next-generation system.\u003C\u002Fp>\n\u003Cp>The system was more than ten years old, and much of it was outdated. Since this was a next-generation project, we planned major changes: updating to the latest Spring version and introducing technologies such as AOP and Spring Security that the existing system did not use.\u003C\u002Fp>\n\u003Cp>But there was one area where we could not compromise.\u003C\u002Fp>\n\u003Cp>That was \u003Cstrong>compatibility\u003C\u002Fstrong>.\u003C\u002Fp>\n\u003Ch2 id=\"external-users-may-not-welcome-change\">External Users May Not Welcome Change\u003C\u002Fh2>\n\u003Cp>The legacy system I operated used its own XML format. It was difficult to read and cumbersome to write request bodies for. If it had been up to me alone, I would have removed all the XML.\u003C\u002Fp>\n\u003Cp>Most new external users also preferred JSON, the web standard. But removing a working feature just because it was legacy would force existing users to develop changes on their side. That would impose a cost on someone.\u003C\u002Fp>\n\u003Cp>Depending on the interests involved, some users also asked us to cover that cost. Through an internal decision, we set the following direction for the renewal.\u003C\u002Fp>\n\u003Cp>Build a system that is efficient and easy to maintain, but make the change \u003Cstrong>as unnoticeable as possible to external users\u003C\u002Fstrong>.\u003C\u002Fp>\n\u003Ch2 id=\"pursuing-both-goals\">Pursuing Both Goals\u003C\u002Fh2>\n\u003Cp>Once the direction was set, the first thing I did during the design phase was remove concerns that developers should not have to handle.\u003C\u002Fp>\n\u003Cp>In the legacy system, code for tasks such as authentication and exception handling was mixed into the service logic. Separating these repeated parts into common layers would make the system much easier to maintain. Developers could then focus on the business logic.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Authentication and authorization → Spring Security\u003C\u002Fli>\n\u003Cli>Exception handling → ControllerAdvice\u003C\u002Fli>\n\u003Cli>Success and failure logging → AOP\u003C\u002Fli>\n\u003Cli>API response generation → ResponseBodyAdvice\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>At the same time, API response generation required the most attention to preserve external compatibility.\u003C\u002Fp>\n\u003Cp>Because the legacy system used a proprietary XML format, it could not use the XML parser provided by the Spring Framework. I needed a design that supported both XML and JSON while converting request and response bodies to and from Java objects. This was also the part where I received the most help from AI.\u003C\u002Fp>\n\u003Cp>I implemented this in ResponseBodyAdvice so that each external user received the response format specified by the \u003Ccode>Accept\u003C\u002Fcode> header.\u003C\u002Fp>\n\u003Cpre class=\"shiki github-light\" style=\"background-color:#fff;color:#24292e\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#B31D28;font-style:italic\">Root\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">  &#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#B31D28;font-style:italic\">Set\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1\"> id\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\">=\u003C\u002Fspan>\u003Cspan style=\"color:#032F62\">\"product\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">    &#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#B31D28;font-style:italic\">Rows\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">      &#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#B31D28;font-style:italic\">Row\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">        &#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#B31D28;font-style:italic\">itemCode\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\">>PD123456&#x3C;\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#B31D28;font-style:italic\">itemCode\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">        &#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#B31D28;font-style:italic\">price\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\">>12000&#x3C;\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#B31D28;font-style:italic\">price\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">      &#x3C;\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#B31D28;font-style:italic\">Row\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">    &#x3C;\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#B31D28;font-style:italic\">Rows\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">  &#x3C;\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#B31D28;font-style:italic\">Set\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">&#x3C;\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#B31D28;font-style:italic\">Root\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">{ \"product\": { \"itemCode\": \"PD123456\", \"price\": 12000 } }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003Cpre class=\"shiki github-light\" style=\"background-color:#fff;color:#24292e\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49\">class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1\"> ProductUpdateRequest\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49\">    class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1\"> Product\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49\">        private\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\"> String itemCode;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49\">        private\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\"> Integer price;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003Cpre class=\"shiki github-light\" style=\"background-color:#fff;color:#24292e\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">@\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49\">RestControllerAdvice\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1\"> XmlResponseAdvice\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1\"> ResponseBodyAdvice\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49\">Object\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\">> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49\"> boolean\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1\"> supports\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\">(MethodParameter \u003C\u002Fspan>\u003Cspan style=\"color:#E36209\">returnType\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">                            Class&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49\">?\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49\"> extends\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\"> HttpMessageConverter&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49\">?\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\">>> \u003C\u002Fspan>\u003Cspan style=\"color:#E36209\">converterType\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">        ...\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\"> Object \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1\">beforeBodyWrite\u003C\u002Fspan>\u003Cspan style=\"color:#24292E\">(\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">                ...\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E\">        ) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">            \u002F\u002F some custom logic\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>With this approach, existing external users did not have to change anything. New external users could integrate with the API in JSON by changing only the \u003Ccode>Accept\u003C\u002Fcode> header.\u003C\u002Fp>\n\u003Cp>It seemed that external users would be able to move to the new API without much difficulty. Then another problem began to surface.\u003C\u002Fp>\n\u003Ch2 id=\"api-documentation\">API Documentation\u003C\u002Fh2>\n\u003Cp>Documentation for the legacy API was also managed in Excel. Whenever an API request or response specification changed, we had to update the document and send it to external users.\u003C\u002Fp>\n\u003Cp>As the project added new features and changed some specifications, the gap between the actual API and the documentation kept growing. Emails from external users asking for the latest documentation began to pile up and create a bottleneck in our work.\u003C\u002Fp>\n\u003Cp>I wanted to replace the old practice of sharing Excel documents by email. A readable UI would provide a much better experience for external users, and no longer maintaining separate documents would also reduce our workload.\u003C\u002Fp>\n\u003Cp>That led us to build a new developer portal. Using the open-source Docusaurus project and integrating it with Swagger produced results faster than I expected.\u003C\u002Fp>\n\u003Cp>It began as a way to convert Excel documents into web-based API documentation, but we added needed features one at a time, including notices and a change history.\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"__WORK_ASSET__legacy-transition-portal.webp\" alt=\"\" loading=\"lazy\">\u003C\u002Fp>\n\u003Cp>In the end, I was satisfied enough to wonder why we had not introduced it sooner.\u003C\u002Fp>\n\u003Cp>The documentation is updated automatically with Swagger, which reduces operational work. External users can also view the change history and notices immediately on the web, making the workflow much more efficient.\u003C\u002Fp>\n\u003Ch2 id=\"closing-thoughts\">Closing Thoughts\u003C\u002Fh2>\n\u003Cp>Adopting new technology is important, but for a developer operating a service, it also seems important to make changes without passing the cost on to existing external users.\u003C\u002Fp>\n\u003Cp>Sometimes, of course, a bold decision is necessary. Replacing Excel documents with a web-based system was one such decision.\u003C\u002Fp>\n\u003Cp>Migration cost, business continuity, and user experience.\u003C\u002Fp>\n\u003Cp>Perhaps one of the responsibilities of operating a service is to weigh what will be gained and lost from different perspectives and find a middle ground.\u003C\u002Fp>\n"]