বসন্তের সাথে কীভাবে REST এপিআই সংস্করণ পরিচালনা করবেন?


118

আমি স্প্রিং ৩.২.x ব্যবহার করে কীভাবে একটি আরএসটি এপিআই সংস্করণ পরিচালনা করব তা অনুসন্ধান করেছি, তবে বজায় রাখা সহজ এমন কিছুই আমি পাইনি। আমি প্রথমে আমার সমস্যাটি এবং তারপরে একটি সমাধান ব্যাখ্যা করব ... তবে আমি ভাবছি যে আমি এখানে চাকাটি পুনরায় উদ্ভাবন করছি কিনা।

আমি গ্রহণ শিরোনামের উপর ভিত্তি করে সংস্করণটি পরিচালনা করতে চাই এবং উদাহরণস্বরূপ যদি কোনও অনুরোধের কাছে গ্রহণ শিরোনাম থাকে তবে application/vnd.company.app-1.1+jsonআমি চাই যে বসন্তের এমভিসি এই সংস্করণটি পরিচালনা করে এমন পদ্ধতিতে এটি ফরোয়ার্ড করে। এবং যেহেতু কোনও এপিআইতে সমস্ত পদ্ধতি একই রিলিজে পরিবর্তিত হয় না, তাই আমি আমার প্রতিটি কন্ট্রোলারের কাছে যেতে এবং কোনও হ্যান্ডলারের জন্য কোনও সংস্করণ পরিবর্তন করতে চাই না যা সংস্করণগুলির মধ্যে পরিবর্তিত হয়নি। স্প্রিং ইতিমধ্যে কোন পদ্ধতিটি কল করতে হবে তা আবিষ্কার করেছে বলে নিজেরাই কন্ট্রোলারে (পরিষেবা লোকের ব্যবহার করে) কোন সংস্করণটি ব্যবহার করা হবে তা নির্ধারণ করার যুক্তিও রাখতে চাই না।

সুতরাং সংস্করণ 1.0, 1.8 তে একটি এপিআই নিয়েছে যেখানে হ্যান্ডলারটি 1.0 সংস্করণে প্রবর্তিত হয়েছিল এবং v1.7 এ পরিবর্তিত হয়েছে, আমি এটি নিম্নলিখিত পদ্ধতিতে পরিচালনা করতে চাই। কল্পনা করুন যে কোডটি একটি নিয়ামকের ভিতরে রয়েছে এবং এমন একটি কোড রয়েছে যা শিরোলেখ থেকে সংস্করণটি বের করতে সক্ষম। (নিম্নলিখিতটি বসন্তে অবৈধ)

@RequestMapping(...)
@VersionRange(1.0,1.6)
@ResponseBody
public Object method1() {
   // so something
   return object;
}

@RequestMapping(...) //same Request mapping annotation
@VersionRange(1.7)
@ResponseBody
public Object method2() {
   // so something
   return object;
}

বসন্তে এটি সম্ভব নয় কারণ 2 টি পদ্ধতির একই RequestMappingটীকা থাকে এবং স্প্রিং লোড হতে ব্যর্থ হয়। ধারণাটি হ'ল VersionRangeটীকাটি একটি মুক্ত বা বন্ধ সংস্করণ পরিসরটি সংজ্ঞায়িত করতে পারে। প্রথম পদ্ধতিটি 1.0 থেকে 1.6 সংস্করণগুলির মধ্যে বৈধ, অন্যদিকে 1.7 সংস্করণটির জন্য (সর্বশেষতম সংস্করণ 1.8 সহ)। আমি জানি যে কেউ যদি 99.99 সংস্করণটি পাস করার সিদ্ধান্ত নেয় তবে এই পদ্ধতির ব্যত্যয় ঘটেছে, তবে এটির সাথে আমি বেঁচে আছি ঠিক।

এখন, যেহেতু উপরের অংশে বসন্ত কীভাবে কাজ করে তার গুরুতর পুনরায় কাজ করা সম্ভব নয়, তাই হ্যান্ডলাররা যেভাবে অনুরোধগুলির সাথে মিলেছে, বিশেষত আমার নিজের লেখার জন্য ProducesRequestConditionএবং সেখানে সংস্করণটির সীমা রয়েছে তার সাথে আমি টিংগার করার কথা ভাবছিলাম । উদাহরণ স্বরূপ

কোড:

@RequestMapping(..., produces = "application/vnd.company.app-[1.0-1.6]+json)
@ResponseBody
public Object method1() {
   // so something
   return object;
}

@RequestMapping(..., produces = "application/vnd.company.app-[1.7-]+json)
@ResponseBody
public Object method2() {
   // so something
   return object;
}

এইভাবে, আমি টীকাগুলির উত্পাদনের অংশে সংজ্ঞায়িত বা উন্মুক্ত সংস্করণ রেঞ্জ রাখতে পারি। আমি এই সমাধানটিতে এখনই কাজ করছি, যে সমস্যাটি এখনও আমার এখনও পছন্দ হয়নি এমন কয়েকটি মূল স্প্রিং এমভিসি ক্লাস ( RequestMappingInfoHandlerMapping, RequestMappingHandlerMappingএবং RequestMappingInfo) প্রতিস্থাপন করতে হয়েছিল , কারণ এটি যখনই নতুন সংস্করণে আপগ্রেড করার সিদ্ধান্ত নেয় তখন অতিরিক্ত কাজ করা হয় means বসন্ত।

আমি যে কোনও ধারণার প্রশংসা করব ... এবং বিশেষত, সরল, সহজ উপায় বজায় রাখার জন্য এটি করার কোনও পরামর্শ


সম্পাদন করা

একটি অনুগ্রহ যোগ করা। অনুগ্রহ পেতে, দয়া করে নিজেরাই নিয়ন্ত্রকের মধ্যে এই যুক্তি না রাখার পরামর্শ দিয়ে উপরের প্রশ্নের উত্তর দিন। কোন নিয়ামক পদ্ধতিটি কল করতে হবে তা নির্বাচন করার জন্য বসন্তের ইতিমধ্যে প্রচুর যুক্তি রয়েছে এবং আমি এটিতে পিগিব্যাক করতে চাই।


সম্পাদনা 2

আমি গিথুবে মূল POC (কিছু উন্নতির সাথে) ভাগ করেছি: https://github.com/augusto/restVersioning



1
@ ফ্লুপ আমি আপনার মন্তব্য বুঝতে পারি না। এটি কেবলমাত্র বলেছে যে আপনি শিরোনাম ব্যবহার করতে পারেন এবং যেমনটি আমি বলেছিলাম, বসন্তটি বাক্সের বাইরে যা সরবরাহ করে তা নিয়মিত আপডেট হওয়া এপিআই সমর্থন করতে যথেষ্ট নয়। আরও খারাপ এই উত্তরটির লিঙ্কটি ইউআরএল সংস্করণ ব্যবহার করে।
আগস্টো

সম্ভবত আপনি যা খুঁজছেন ঠিক তা নয়, তবে স্প্রিংস 3.2 অনুরোধম্যাপিংয়ে "উত্পাদিত" প্যারামিটার সমর্থন করে। একটি সতর্কতা হ'ল সংস্করণ তালিকাটি স্পষ্ট করে দিতে হবে। যেমন, produces={"application/json-1.0", "application/json-1.1"}ইত্যাদি
বিমসপি

1
আমাদের আমাদের এপিআইয়ের বেশ কয়েকটি সংস্করণ সমর্থন করা দরকার, এই পার্থক্যগুলি সাধারণত ছোটখাটো পরিবর্তন যা কিছু ক্লায়েন্টের বেমানান না থেকে কল আসে (এটি যদি আমাদের 4 টি ছোট সংস্করণকে সমর্থন করার প্রয়োজন হয় তবে এটি আশ্চর্যজনক হবে না, যার মধ্যে কিছু শেষ পয়েন্টগুলি অসম্পূর্ণ)। আমি এটি ইউআরএল রাখার পরামর্শটির প্রশংসা করি, তবে আমরা জানি যে এটি ভুল দিকের এক ধাপ, কারণ আমাদের ইউআরএল সংস্করণ সহ বেশ কয়েকটি অ্যাপ রয়েছে এবং প্রতিবার যখন আমাদের বার বার করার দরকার হয় তখন সেখানে প্রচুর কাজ জড়িত থাকে সংস্করণ।
আগস্টো

1
@ অগস্টো, আপনি আসলে খুব বেশি কিছু করেন নি। আপনার এপিআই পরিবর্তনগুলি এমনভাবে ডিজাইন করুন যা পশ্চাদগম্য সামঞ্জস্যতা ভঙ্গ করে না। কেবল আমাকে এমন পরিবর্তনগুলির উদাহরণ দিন যা সামঞ্জস্যতা ভঙ্গ করে এবং আমি আপনাকে প্রদর্শন করব কীভাবে নন-ব্রেকিং ফ্যাশনে এই পরিবর্তনগুলি করা যায়।
আলেক্সি অ্যান্ড্রিভ

উত্তর:


62

পিছনে সামঞ্জস্যপূর্ণ পরিবর্তন করে সংস্করণটিকে এড়ানো যায় কিনা (যা আপনি যখন কোনও কর্পোরেট নির্দেশিকা দ্বারা আবদ্ধ হন বা আপনার এপিআই ক্লায়েন্টগুলি বগি পদ্ধতিতে প্রয়োগ করা হয় এবং তা না হলেও ভেঙে ফেলা যায়) বিমূর্ত প্রয়োজনীয়তা একটি আকর্ষণীয় এক:

পদ্ধতি শরীরে মূল্যায়ন না করে অনুরোধ থেকে শিরোনামের মানগুলি নির্বিচারে মূল্যায়ন করে এমন একটি কাস্টম অনুরোধ ম্যাপিং আমি কীভাবে করব?

এই এসও উত্তরে বর্ণিত হিসাবে আপনি আসলে একই থাকতে পারেন @RequestMappingএবং রানটাইমের সময় ঘটে যাওয়া আসল রাউটিংয়ের সময় পার্থক্য করতে আলাদা টীকাটি ব্যবহার করতে পারেন । এটি করতে, আপনাকে নিম্নলিখিতগুলি করতে হবে:

  1. একটি নতুন টীকা তৈরি করুন VersionRange
  2. বাস্তবায়ন a RequestCondition<VersionRange>। যেহেতু আপনার কাছে সেরা-ম্যাচের অ্যালগরিদমের মতো কিছু থাকবে আপনার অন্য পরীক্ষাগুলির সাথে বর্ণিত পদ্ধতিগুলি VersionRangeবর্তমান অনুরোধের জন্য আরও ভাল ম্যাচ সরবরাহ করে কিনা তা আপনাকে পরীক্ষা করতে হবে ।
  3. VersionRangeRequestMappingHandlerMappingটীকা এবং অনুরোধ শর্তের উপর ভিত্তি করে একটি প্রয়োগ করুন (পোস্টে বর্ণিত হিসাবে কীভাবে @RequestMapping কাস্টম বৈশিষ্ট্যগুলি প্রয়োগ করতে হবে )।
  4. VersionRangeRequestMappingHandlerMappingডিফল্ট ব্যবহারের আগে আপনার মূল্যায়ন করার জন্য বসন্তটি কনফিগার করুন RequestMappingHandlerMapping(উদাহরণস্বরূপ এর অর্ডার 0 তে সেট করে)।

এর জন্য স্প্রিং উপাদানগুলির কোনও হ্যাকি প্রতিস্থাপনের প্রয়োজন হবে না তবে এটি বসন্তের কনফিগারেশন এবং এক্সটেনশন প্রক্রিয়াগুলি ব্যবহার করে যাতে আপনার স্প্রিং সংস্করণটি আপডেট করা হলেও এটি কাজ করা উচিত (যতক্ষণ না নতুন সংস্করণ এই ব্যবস্থাগুলি সমর্থন করে)।


উত্তর xWker হিসাবে আপনার মন্তব্য যোগ করার জন্য ধন্যবাদ। এখন পর্যন্ত সেরা এক। আপনি উল্লিখিত লিঙ্কগুলির উপর ভিত্তি করে সমাধানটি কার্যকর করেছি এবং এটি খুব খারাপ নয়। স্প্রিংয়ের নতুন সংস্করণে আপগ্রেড করার সময় সবচেয়ে বড় সমস্যাটি প্রকাশিত হবে কারণ এর পিছনে যুক্তিতে কোনও পরিবর্তন পরীক্ষা করতে হবে mvc:annotation-driven। আশা করি স্প্রিং এমন একটি সংস্করণ সরবরাহ করবে mvc:annotation-drivenযার মধ্যে কাস্টম শর্তগুলি সংজ্ঞায়িত করতে পারে।
আগস্টো

@ অগস্টো, আধা বছর পরে, এটি আপনার জন্য কীভাবে কাজ করছে? এছাড়াও, আমি কৌতূহলী, আপনি কি প্রতি-পদ্ধতি ভিত্তিতে সত্যই সংস্করণ করছেন? এই মুহুর্তে আমি ভাবছি যে এটি প্রতি শ্রেণি / প্রতি-নিয়ামক স্তরের গ্রানুলারিটির সংস্করণে আরও পরিষ্কার হবে না?
Sander Verhagen

1
@ স্যান্ডারভেরহেন এটি কাজ করছে, তবে আমরা পদ্ধতি বা নিয়ামক হিসাবে নয়, পুরো এপিআই সংস্করণ করি (ব্যবসায়ের এক দিকের উপর দৃষ্টি নিবদ্ধ রাখার কারণে এপিআইটি যথেষ্ট ছোট)। আমাদের কাছে যথেষ্ট বড় প্রকল্প রয়েছে যেখানে তারা সংস্থান অনুসারে আলাদা সংস্করণ ব্যবহার করতে বেছে নিয়েছে এবং ইউআরএল-তে নির্দিষ্ট করে দিয়েছে (যাতে আপনার / / ভি 1 / সেশনের একটি শেষ পয়েন্ট থাকতে পারে এবং সম্পূর্ণ ভিন্ন সংস্করণে অন্য কোনও সংস্থান থাকতে পারে, যেমন / v4 / অর্ডার) ... এটি কিছুটা নমনীয়, তবে ক্লায়েন্টদের প্রতিটি প্রান্তের বিন্দুতে কোন সংস্করণটি কল করা উচিত তা জানতে এটি আরও চাপ দেয়।
আগস্টো

1
দুর্ভাগ্যক্রমে, সোয়াগারের সাথে এটি দুর্দান্ত খেলছে না, কারণ ওয়েবএমভিসি কনফিগারেশনসপোর্ট প্রসারিত করার সময় প্রচুর অটো কনফিগারেশন বন্ধ রয়েছে।
রিক

আমি এই সমাধানটি চেষ্টা করেছিলাম কিন্তু এটি আসলে ২.৩.২.আরেলিয়াসের সাথে কাজ করছে না। আপনার কাছে কি দেখানোর মতো কোনও উদাহরণ রয়েছে?
প্যাট্রিক

54

আমি কেবল একটি কাস্টম সমাধান তৈরি করেছি। আমি ক্লাসের অভ্যন্তরে টীকা @ApiVersionসংমিশ্রণে টিকা ব্যবহার করছি ।@RequestMapping@Controller

উদাহরণ:

@Controller
@RequestMapping("x")
@ApiVersion(1)
class MyController {

    @RequestMapping("a")
    void a() {}         // maps to /v1/x/a

    @RequestMapping("b")
    @ApiVersion(2)
    void b() {}         // maps to /v2/x/b

    @RequestMapping("c")
    @ApiVersion({1,3})
    void c() {}         // maps to /v1/x/c
                        //  and to /v3/x/c

}

বাস্তবায়ন:

অ্যাপি ভার্সন.জভা টিকা:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiVersion {
    int[] value();
}

ApiVersionRequestMappingHandlerMapping.java (এটি বেশিরভাগ অনুলিপি করে এখান থেকে আটকানো হয় RequestMappingHandlerMapping):

public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

    private final String prefix;

    public ApiVersionRequestMappingHandlerMapping(String prefix) {
        this.prefix = prefix;
    }

    @Override
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        RequestMappingInfo info = super.getMappingForMethod(method, handlerType);
        if(info == null) return null;

        ApiVersion methodAnnotation = AnnotationUtils.findAnnotation(method, ApiVersion.class);
        if(methodAnnotation != null) {
            RequestCondition<?> methodCondition = getCustomMethodCondition(method);
            // Concatenate our ApiVersion with the usual request mapping
            info = createApiVersionInfo(methodAnnotation, methodCondition).combine(info);
        } else {
            ApiVersion typeAnnotation = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
            if(typeAnnotation != null) {
                RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);
                // Concatenate our ApiVersion with the usual request mapping
                info = createApiVersionInfo(typeAnnotation, typeCondition).combine(info);
            }
        }

        return info;
    }

    private RequestMappingInfo createApiVersionInfo(ApiVersion annotation, RequestCondition<?> customCondition) {
        int[] values = annotation.value();
        String[] patterns = new String[values.length];
        for(int i=0; i<values.length; i++) {
            // Build the URL prefix
            patterns[i] = prefix+values[i]; 
        }

        return new RequestMappingInfo(
                new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(), useSuffixPatternMatch(), useTrailingSlashMatch(), getFileExtensions()),
                new RequestMethodsRequestCondition(),
                new ParamsRequestCondition(),
                new HeadersRequestCondition(),
                new ConsumesRequestCondition(),
                new ProducesRequestCondition(),
                customCondition);
    }

}

ওয়েবএমভিসি কনফিগারেশনের সহায়তা ইনজেকশন:

public class WebMvcConfig extends WebMvcConfigurationSupport {
    @Override
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        return new ApiVersionRequestMappingHandlerMapping("v");
    }
}

4
"1.2" এর মতো সংস্করণগুলিকে অনুমতি দেওয়ার জন্য আমি স্ট্রিং [] তে ইনট পরিবর্তন করেছি এবং তাই আমি "সর্বশেষ" এর মতো কীওয়ার্ডগুলি হ্যান্ডেল করতে পারি
মেলিগ

3
হ্যাঁ, এটি বেশ যুক্তিসঙ্গত। ভবিষ্যতের প্রকল্পগুলির জন্য, আমি কিছু কারণে আলাদাভাবে যেতে চাই: ১. ইউআরএল সংস্থানগুলি উপস্থাপন করে। /v1/aResourceএবং /v2/aResourceবিভিন্ন সংস্থার মতো দেখতে, তবে এটি একই সংস্থানটির আলাদা উপস্থাপনা! ২) এইচটিটিপি শিরোনাম ব্যবহার করা আরও ভাল দেখাচ্ছে তবে আপনি কাউকে URL দিতে পারবেন না, কারণ URL টি শিরোনামটি নেই। ৩. ইউআরএল প্যারামিটার ব্যবহার করা, অর্থাৎ /aResource?v=2.1(বিটিডব্লিউ: গুগলের সংস্করণটি সেভাবে হয়)। ...আমি এখনও নিশ্চিত নই যে আমি 2 বা 3 বিকল্পের সাথে যাব কিনা তবে উপরে বর্ণিত কারণে আমি আর কখনও 1 ব্যবহার করব না ।
বেনিয়ামিন এম

5
আপনি নিজের ইনজেকশন করতে চান RequestMappingHandlerMapping মধ্যে আপনার পরিবর্তে WebMvcConfigurationওভাররাইট createRequestMappingHandlerMappingকরা উচিত requestMappingHandlerMapping! অন্যথায় আপনি অদ্ভুত সমস্যাগুলির মুখোমুখি হবেন (একটি বন্ধ অধিবেশনের কারণে হাইবারনেটস অলস
সূচনাতে

1
পদ্ধতিরটি দেখতে দুর্দান্ত দেখাচ্ছে তবে একরকম মনে হচ্ছে এটি জন্টি পরীক্ষার ক্ষেত্রে (স্প্রিংআর্নার) কাজ করে না। পরীক্ষার মামলাগুলির সাথে কাজ করার যে কোনও সুযোগ আপনি পেয়েছেন
জেদেব

1
এই কাজটি করার একটি উপায় আছে, প্রসারিত না করে WebMvcConfigurationSupport প্রসারিত করুন DelegatingWebMvcConfiguration। এই (দেখুন আমার জন্য কাজ stackoverflow.com/questions/22267191/... )
SeB.Fr

17

আমি তখনও সংস্করণটির জন্য ইউআরএলগুলি ব্যবহারের পরামর্শ দেব কারণ URLগুলিতে @RequestMapping নিদর্শন এবং পাথ প্যারামিটারগুলিকে সমর্থন করে, কোন ফর্ম্যাটটি রেজিপেক্সের সাহায্যে নির্দিষ্ট করা যেতে পারে।

এবং ক্লায়েন্ট আপগ্রেডগুলি পরিচালনা করতে (যা আপনি মন্তব্যে উল্লেখ করেছেন) আপনি 'সর্বশেষ' এর মতো উপকরণ ব্যবহার করতে পারেন। বা এপিআই-এর রূপান্তরিত সংস্করণ রয়েছে যা সর্বশেষ সংস্করণ (হ্যাঁ) ব্যবহার করে।

এছাড়াও পাথ প্যারামিটারগুলি ব্যবহার করে আপনি যে কোনও জটিল সংস্করণ হ্যান্ডলিং লজিক বাস্তবায়ন করতে পারেন এবং যদি আপনি ইতিমধ্যে ব্যাপ্তি রাখতে চান তবে আপনি খুব শীঘ্রই আরও কিছু চাইবেন।

এখানে কয়েকটি উদাহরণ দেওয়া হল:

@RequestMapping({
    "/**/public_api/1.1/method",
    "/**/public_api/1.2/method",
})
public void method1(){
}

@RequestMapping({
    "/**/public_api/1.3/method"
    "/**/public_api/latest/method"
    "/**/public_api/method" 
})
public void method2(){
}

@RequestMapping({
    "/**/public_api/1.4/method"
    "/**/public_api/beta/method"
})
public void method2(){
}

//handles all 1.* requests
@RequestMapping({
    "/**/public_api/{version:1\\.\\d+}/method"
})
public void methodManual1(@PathVariable("version") String version){
}

//handles 1.0-1.6 range, but somewhat ugly
@RequestMapping({
    "/**/public_api/{version:1\\.[0123456]?}/method"
})
public void methodManual1(@PathVariable("version") String version){
}

//fully manual version handling
@RequestMapping({
    "/**/public_api/{version}/method"
})
public void methodManual2(@PathVariable("version") String version){
    int[] versionParts = getVersionParts(version);
    //manual handling of versions
}

public int[] getVersionParts(String version){
    try{
        String[] versionParts = version.split("\\.");
        int[] result = new int[versionParts.length];
        for(int i=0;i<versionParts.length;i++){
            result[i] = Integer.parseInt(versionParts[i]);
        }
        return result;
    }catch (Exception ex) {
        return null;
    }
}

শেষ পদ্ধতির উপর ভিত্তি করে আপনি যা চান তা বাস্তবে বাস্তবায়ন করতে পারেন।

উদাহরণস্বরূপ আপনার কাছে এমন একটি নিয়ামক থাকতে পারে যাতে সংস্করণ হ্যান্ডলিং সহ কেবল পদ্ধতি স্ট্যাব থাকে।

সেই হ্যান্ডলিংয়ের ক্ষেত্রে আপনি কিছু বসন্ত পরিষেবা / উপাদানে বা একই নাম / স্বাক্ষরযুক্ত এবং প্রয়োজনীয় সংস্করণ @ ভার্সনরেঞ্জ সহ একই শ্রেণিতে দেখতে পাবেন (প্রতিবিম্ব / এওপি / কোড জেনারেশন লাইব্রেরি ব্যবহার করে) এবং এটি সমস্ত পরামিতিগুলি পেরিয়ে যাওয়ার জন্য অনুরোধ করছেন।


14

আমি একটি সমাধান কার্যকর করেছি যা বাকী সংস্করণে সমস্যাটি পুরোপুরি পরিচালনা করে।

সাধারণ বক্তৃতা বিশিষ্ট সংস্করণের জন্য তিনটি প্রধান পন্থা রয়েছে:

  • পাথ ভিত্তিক অ্যাপ্রোচ, ক্লায়েন্টটি ইউআরএলটিতে সংস্করণটি সংজ্ঞায়িত করে:

    http://localhost:9001/api/v1/user
    http://localhost:9001/api/v2/user
  • সামগ্রী-প্রকারের শিরোনাম, যাতে ক্লায়েন্ট গ্রহণ শিরোনামে সংস্করণটি সংজ্ঞায়িত করে :

    http://localhost:9001/api/v1/user with 
    Accept: application/vnd.app-1.0+json OR application/vnd.app-2.0+json
  • কাস্টম শিরোনাম , যাতে ক্লায়েন্ট একটি কাস্টম শিরোনামে সংস্করণটি সংজ্ঞায়িত করে।

সমস্যা সঙ্গে প্রথম পদ্ধতির যে আপনি যদি সংস্করণ পরিবর্তন v1 এ থেকে বলা যাক হল -> v2, সম্ভবত আপনি v1 এ সম্পদ যে v2 পথে পরিবর্তন করা কপি-পেস্ট করতে

সমস্যা সঙ্গে দ্বিতীয় পদ্ধতির যে মত কিছু টুলস http://swagger.io/ একই পথ কিন্তু বিভিন্ন সামগ্রী-প্রকার (চেক সমস্যার সাথে অপারেশন মধ্যে স্বতন্ত্র পারবনা https://github.com/OAI/OpenAPI-Specification/issues/ 146 )

সমাধান

যেহেতু আমি বিশ্রামের ডকুমেন্টেশন সরঞ্জামগুলির সাথে অনেক বেশি কাজ করছি, তাই আমি প্রথম পদ্ধতির ব্যবহার করতে পছন্দ করি। আমার সমাধানটি প্রথম পদ্ধতির সাহায্যে সমস্যাটি পরিচালনা করে , সুতরাং আপনাকে শেষ সংস্করণটিকে নতুন সংস্করণে অনুলিপি করতে হবে না।

ধরা যাক আমাদের কাছে ব্যবহারকারী কন্ট্রোলারের জন্য ভি 1 এবং ভি 2 সংস্করণ রয়েছে:

package com.mspapant.example.restVersion.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * The user controller.
 *
 * @author : Manos Papantonakos on 19/8/2016.
 */
@Controller
@Api(value = "user", description = "Operations about users")
public class UserController {

    /**
     * Return the user.
     *
     * @return the user
     */
    @ResponseBody
    @RequestMapping(method = RequestMethod.GET, value = "/api/v1/user")
    @ApiOperation(value = "Returns user", notes = "Returns the user", tags = {"GET", "User"})
    public String getUserV1() {
         return "User V1";
    }

    /**
     * Return the user.
     *
     * @return the user
     */
    @ResponseBody
    @RequestMapping(method = RequestMethod.GET, value = "/api/v2/user")
    @ApiOperation(value = "Returns user", notes = "Returns the user", tags = {"GET", "User"})
    public String getUserV2() {
         return "User V2";
    }
 }

প্রয়োজন যদি আমি অনুরোধ v1 এ ব্যবহারকারী রিসোর্স আমাকে গ্রহণ করতে হবে জন্য "ব্যবহারকারী V1 থেকে" repsonse, আমি অনুরোধ অন্যথায় যদি v2 , v3 এর এবং তাই আমি গ্রহণ করতে হবে উপর "ব্যবহারকারী থেকে V2" প্রতিক্রিয়া।

এখানে চিত্র বর্ণনা লিখুন

বসন্তে এটি বাস্তবায়নের জন্য, আমাদের ডিফল্ট অনুরোধম্যাপিংহ্যান্ডলারম্যাপিং আচরণটি ওভাররাইড করতে হবে :

package com.mspapant.example.restVersion.conf.mapping;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class VersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

    @Value("${server.apiContext}")
    private String apiContext;

    @Value("${server.versionContext}")
    private String versionContext;

    @Override
    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        HandlerMethod method = super.lookupHandlerMethod(lookupPath, request);
        if (method == null && lookupPath.contains(getApiAndVersionContext())) {
            String afterAPIURL = lookupPath.substring(lookupPath.indexOf(getApiAndVersionContext()) + getApiAndVersionContext().length());
            String version = afterAPIURL.substring(0, afterAPIURL.indexOf("/"));
            String path = afterAPIURL.substring(version.length() + 1);

            int previousVersion = getPreviousVersion(version);
            if (previousVersion != 0) {
                lookupPath = getApiAndVersionContext() + previousVersion + "/" + path;
                final String lookupFinal = lookupPath;
                return lookupHandlerMethod(lookupPath, new HttpServletRequestWrapper(request) {
                    @Override
                    public String getRequestURI() {
                        return lookupFinal;
                    }

                    @Override
                    public String getServletPath() {
                        return lookupFinal;
                    }});
            }
        }
        return method;
    }

    private String getApiAndVersionContext() {
        return "/" + apiContext + "/" + versionContext;
    }

    private int getPreviousVersion(final String version) {
        return new Integer(version) - 1 ;
    }

}

বাস্তবায়ন URL- এ সংস্করণ সার্চ এবং URL .In ক্ষেত্রে এই URL বিদ্যমান নয় সমাধান করতে (উদাহরণস্বরূপ ক্লায়েন্ট অনুরোধ জন্য বসন্ত থেকে জিজ্ঞেস করল v3 এর ) তারপর আমরা দিয়ে চেষ্টা v2 এবং তাই এক যতক্ষণ না আমরা এটি সাম্প্রতিকতম সংস্করণ রিসোর্সে ।

এই বাস্তবায়ন থেকে সুবিধাগুলি দেখতে, আমাদের বলুন যে আমাদের দুটি সংস্থান আছে: ব্যবহারকারী এবং সংস্থা:

http://localhost:9001/api/v{version}/user
http://localhost:9001/api/v{version}/company

ধরা যাক আমরা সংস্থার "চুক্তি" তে পরিবর্তন করেছি যা ক্লায়েন্টকে ভেঙে দেয়। সুতরাং আমরা বাস্তবায়ন করি http://localhost:9001/api/v2/companyএবং আমরা ক্লায়েন্ট থেকে ভি 1 এর পরিবর্তে ভি 2 এ পরিবর্তন করতে বলি।

সুতরাং ক্লায়েন্টের কাছ থেকে নতুন অনুরোধগুলি হ'ল:

http://localhost:9001/api/v2/user
http://localhost:9001/api/v2/company

পরিবর্তে:

http://localhost:9001/api/v1/user
http://localhost:9001/api/v1/company

এখানে সর্বোত্তম অংশটি হ'ল এই সমাধানের সাথে ক্লায়েন্টটি ভি 1 এর ব্যবহারকারীর তথ্য এবং সংযুক্তি সম্পর্কিত তথ্য ভি 2 এর কাছ থেকে ব্যবহারকারী ভি 2 থেকে নতুন (একই) এন্ডপয়েন্ট তৈরি করার প্রয়োজন ছাড়াই পাবেন!

রেস্ট ডকুমেন্টেশন যেমন আমি ইউআরএল-ভিত্তিক সংস্করণ পদ্ধতির নির্বাচন করার কারণ আগে বলেছিলাম যে সোয়াগারের মতো কিছু সরঞ্জাম একই ইউআরএলের সাথে শেষের পয়েন্টগুলিকে ভিন্নভাবে নথি করে না তবে বিভিন্ন সামগ্রীর ধরণের হয়। এই সমাধানের সাথে, উভয় প্রান্তই প্রদর্শিত হবে যেহেতু আলাদা ইউআরএল রয়েছে:

এখানে চিত্র বর্ণনা লিখুন

এলেবেলে

সমাধান বাস্তবায়ন এখানে: https://github.com/mspapant/restVersioningExample/


9

@RequestMappingটীকা একটি সমর্থন headersউপাদান যে আপনার ম্যাচিং অনুরোধ সংকীর্ণ করতে পারবেন। বিশেষত আপনি Acceptএখানে শিরোলেখ ব্যবহার করতে পারেন ।

@RequestMapping(headers = {
    "Accept=application/vnd.company.app-1.0+json",
    "Accept=application/vnd.company.app-1.1+json"
})

এটি ঠিক আপনি যা বর্ণনা করছেন তা নয়, কারণ এটি সরাসরি ব্যাপ্তিগুলি পরিচালনা করে না, তবে উপাদানটি * ওয়াইল্ডকার্ডকেও সমর্থন করে! =। সুতরাং কমপক্ষে আপনি এমন ক্ষেত্রে ওয়াইল্ডকার্ড ব্যবহার করে দূরে সরে যেতে পারেন যেখানে সমস্ত সংস্করণ প্রশ্নাবলীর শেষ পয়েন্টটিকে সমর্থন করে বা প্রদত্ত প্রধান সংস্করণের সমস্ত ছোটখাটো সংস্করণ (যেমন 1. * *) সমর্থন করে।

আমি মনে করি না যে আমি আসলে এই উপাদানটি আগে ব্যবহার করেছি (যদি আমার মনে থাকে না) তবে আমি কেবলমাত্র ডকুমেন্টেশনটি বন্ধ করে দিচ্ছি

http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html


2
আমি এটি সম্পর্কে জানি, তবে আপনি যেমন লিখেছেন, প্রতিটি সংস্করণে আমার সমস্ত কন্ট্রোলারের কাছে গিয়ে একটি সংস্করণ যুক্ত করা দরকার, এমনকি যদি সেগুলি পরিবর্তন না হয়। আপনি যে ব্যাপ্তিটি উল্লেখ করেছেন কেবলমাত্র সম্পূর্ণ ধরণের উপর কাজ করে, উদাহরণস্বরূপ application/*এবং প্রকারের অংশগুলি নয়। উদাহরণস্বরূপ নিম্নলিখিত বসন্তে অবৈধ "Accept=application/vnd.company.app-1.*+json"। এভাবেই বসন্ত বর্গ সঙ্গে সম্পর্কযুক্ত MediaTypeকাজ
অগাস্টো

@ অগাস্টো আপনাকে এটি করার প্রয়োজন হয় না। এই পদ্ধতির সাথে, আপনি "এপিআই" সংস্করণ দিচ্ছেন না তবে "শেষ পয়েন্ট"। প্রতিটি প্রান্তে আলাদা সংস্করণ থাকতে পারে। আমার জন্য এটি API সংস্করণের সাথে তুলনা করে API সংস্করণের সহজতম উপায় । সোয়াগার সেটআপ করা আরও সহজ । এই কৌশলটি বিষয়বস্তু আলোচনার মাধ্যমে সংস্করণ বলা হয়।
ধেরিক

3

মডেল সংস্করণে উত্তরাধিকার ব্যবহার সম্পর্কে কী? এটি আমার প্রকল্পে আমি ব্যবহার করছি এবং এটির জন্য কোনও বিশেষ বসন্তের কনফিগারেশন প্রয়োজন নেই এবং আমি যা চাই ঠিক তা পেয়ে যায়।

@RestController
@RequestMapping(value = "/test/1")
@Deprecated
public class Test1 {
...Fields Getters Setters...
    @RequestMapping(method = RequestMethod.GET)
    @Deprecated
    public Test getTest(Long id) {
        return serviceClass.getTestById(id);
    }
    @RequestMapping(method = RequestMethod.PUT)
    public Test getTest(Test test) {
        return serviceClass.updateTest(test);
    }

}

@RestController
@RequestMapping(value = "/test/2")
public class Test2 extends Test1 {
...Fields Getters Setters...
    @Override
    @RequestMapping(method = RequestMethod.GET)
    public Test getTest(Long id) {
        return serviceClass.getAUpdated(id);
    }

    @RequestMapping(method = RequestMethod.DELETE)
    public Test deleteTest(Long id) {
        return serviceClass.deleteTestById(id);
    }
}

এই সেট আপটি কোডটির সামান্য প্রতিলিপি এবং অল্প কাজের সাথে এপিআইয়ের নতুন সংস্করণগুলিতে পদ্ধতিগুলিকে ওভাররাইট করার ক্ষমতা দেয়। এটি সংস্করণ স্যুইচিং যুক্তি দিয়ে আপনার উত্স কোড জটিল করার প্রয়োজনীয়তাও সংরক্ষণ করে। আপনি যদি কোনও সংস্করণে একটি শেষ পয়েন্ট কোড না করেন তবে এটি পূর্ববর্তী সংস্করণটিকে ডিফল্টরূপে দখল করবে।

অন্যেরা যা করছে তার তুলনায় এটিকে সহজ মনে হচ্ছে। আমি কি অনুপস্থিত কিছু আছে?


1
কোডটি ভাগ করে নেওয়ার জন্য +1। যাইহোক, উত্তরাধিকার এটি দৃly়ভাবে কাপল করে। পরিবর্তে. কন্ট্রোলারগুলি (টেস্ট 1 এবং টেস্ট 2) কেবলমাত্র একটি পাস হতে হবে ... কোনও যুক্তি প্রয়োগ নেই। সমস্ত যুক্তি সার্ভিস ক্লাসে থাকতে হবে, সুমার সার্ভিসে। সেক্ষেত্রে কেবল সাধারণ রচনাটি ব্যবহার করুন এবং অন্য নিয়ামকের কাছ থেকে কখনই উত্তরাধিকারী হন না
ড্যান হুনেক্স

1
@ ড্যান-হুনেক্স মনে হচ্ছে এপিআই-র বিভিন্ন সংস্করণ পরিচালনা করতে সেকেকে উত্তরাধিকারটি ব্যবহার করুন use উত্তরাধিকার সরিয়ে ফেললে সমাধান কী? এবং কেন দৃ in়ভাবে দম্পতি এই উদাহরণে সমস্যা? আমার দৃষ্টিতে, টেস্ট 2 টেস্ট 1 বাড়ায় কারণ এটি এর উন্নতি হয়েছে (একই ভূমিকা এবং একই দায়িত্ব সহ), তাই না?
jeremieca

2

আমি ইতিমধ্যে ইউআরআই সংস্করণ ব্যবহার করে আমার API টি সংস্করণ করার চেষ্টা করেছি , যেমন:

/api/v1/orders
/api/v2/orders

তবে এই কাজটি করার চেষ্টা করার সময় কিছু চ্যালেঞ্জ রয়েছে: কীভাবে আপনার কোডটি বিভিন্ন সংস্করণ দিয়ে সংগঠিত করবেন? একই সাথে দুটি (বা আরও) সংস্করণ কীভাবে পরিচালনা করবেন? কিছু সংস্করণ অপসারণ যখন প্রভাব?

আমি যে সেরা বিকল্পটি পেয়েছি তা হ'ল সম্পূর্ণ এপিআই সংস্করণ নয়, তবে প্রতিটি শেষ পয়েন্টে সংস্করণটি নিয়ন্ত্রণ করুন । এই প্যাটার্নটি কনটেন্ট আলোচনার মাধ্যমে গ্রহণযোগ্য শিরোনাম বা সংস্করণ ব্যবহার করে সংস্করণ বলা হয় :

এই পদ্ধতিটি আমাদের পুরো API টি সংস্করণ করার পরিবর্তে একক সংস্থানীয় উপস্থাপনাকে সংস্করণ করতে দেয় যা আমাদের সংস্করণে আরও দানাদার নিয়ন্ত্রণ দেয়। এটি কোড বেসে একটি ছোট পায়ের ছাপ তৈরি করে কারণ একটি নতুন সংস্করণ তৈরি করার সময় আমাদের পুরো অ্যাপ্লিকেশনটি কাঁটাচামচ করতে হবে না। এই পদ্ধতির আর একটি সুবিধা হ'ল এটির জন্য ইউআরআই পথের মাধ্যমে সংস্করণ প্রবর্তন করে ইউআরআই রাউটিং বিধিগুলি প্রয়োগ করার দরকার নেই implementing

বসন্তে বাস্তবায়ন

প্রথমত, আপনি মৌলিক উত্পাদনের বৈশিষ্ট্যযুক্ত একটি কন্ট্রোলার তৈরি করেন যা শ্রেণীর অভ্যন্তরে প্রতিটি শেষ পয়েন্টের জন্য ডিফল্টরূপে প্রয়োগ করা হবে।

@RestController
@RequestMapping(value = "/api/orders/", produces = "application/vnd.company.etc.v1+json")
public class OrderController {

}

এর পরে, একটি সম্ভাব্য দৃশ্য তৈরি করুন যেখানে আপনার অর্ডার তৈরির জন্য একটি শেষ পয়েন্টের দুটি সংস্করণ রয়েছে:

@Deprecated
@PostMapping
public ResponseEntity<OrderResponse> createV1(
        @RequestBody OrderRequest orderRequest) {

    OrderResponse response = createOrderService.createOrder(orderRequest);
    return new ResponseEntity<>(response, HttpStatus.CREATED);
}

@PostMapping(
        produces = "application/vnd.company.etc.v2+json",
        consumes = "application/vnd.company.etc.v2+json")
public ResponseEntity<OrderResponseV2> createV2(
        @RequestBody OrderRequestV2 orderRequest) {

    OrderResponse response = createOrderService.createOrder(orderRequest);
    return new ResponseEntity<>(response, HttpStatus.CREATED);
}

সম্পন্ন! কাঙ্ক্ষিত এইচটিপিপি শিরোনাম সংস্করণটি ব্যবহার করে কেবল প্রতিটি প্রান্তকে কল করুন :

Content-Type: application/vnd.company.etc.v1+json

অথবা, দুটি সংস্করণ কল করতে:

Content-Type: application/vnd.company.etc.v2+json

আপনার উদ্বেগ সম্পর্কে:

এবং যেহেতু কোনও এপিআইতে সমস্ত পদ্ধতি একই প্রকাশে পরিবর্তিত হয় না, তাই আমি আমার প্রতিটি কন্ট্রোলারের কাছে যেতে চাই না এবং কোনও হ্যান্ডলারের জন্য কিছু পরিবর্তন করতে চাই না যা সংস্করণগুলির মধ্যে পরিবর্তিত হয়নি

যেমন ব্যাখ্যা করা হয়েছে, এই কৌশলটি প্রতিটি নিয়ামক এবং তার প্রকৃত সংস্করণ সহ শেষ পয়েন্ট বজায় রাখে। আপনি কেবলমাত্র শেষের পয়েন্টটি সংশোধন করেছেন যার পরিবর্তন রয়েছে এবং একটি নতুন সংস্করণ প্রয়োজন।

আর সোয়াগার?

বিভিন্ন কৌশল সহ সোয়াগার সেটআপ করাও এই কৌশলটি ব্যবহার করে খুব সহজ। আরও বিশদে এই উত্তরটি দেখুন


1

উত্পাদনে আপনার অবহেলা থাকতে পারে। সুতরাং পদ্ধতি 1 জন্য বলুনproduces="!...1.7" এবং মেথড 2-তে ধনাত্মকতা রয়েছে।

উত্পাদনগুলিও একটি অ্যারে হয় তাই আপনি পদ্ধতি 1 এর জন্য আপনি produces={"...1.6","!...1.7","...1.8"}ইত্যাদি বলতে পারেন (1.7 ব্যতীত সমস্ত গ্রহণ করুন)

অবশ্যই আপনার মনে যে রেঞ্জগুলি রয়েছে তার মতো আদর্শ নয় তবে আমি মনে করি এটি যদি আপনার সিস্টেমে অস্বাভাবিক কিছু হয় তবে অন্যান্য কাস্টম স্টাফের চেয়ে বজায় রাখা সহজ বলে মনে করি। শুভকামনা!


ধন্যবাদ কোডালসা, আমি এমন একটি উপায় সন্ধান করার চেষ্টা করছি যা রক্ষণাবেক্ষণ করা সহজ এবং প্রতিবার আমাদের সংস্করণটি টানানোর জন্য প্রতিটি শেষ পয়েন্টটি আপডেট করার দরকার নেই।
আগস্টো

0

আপনি এওপি ব্যবহার করতে পারেন, ইন্টারসেপশন প্রায়

একটি অনুরোধ ম্যাপিং থাকার কথা বিবেচনা করুন যা /**/public_api/*এই পদ্ধতিতে সমস্ত গ্রহণ করে এবং কিছুই করে না;

@RequestMapping({
    "/**/public_api/*"
})
public void method2(Model model){
}

পরে

@Override
public void around(Method method, Object[] args, Object target)
    throws Throwable {
       // look for the requested version from model parameter, call it desired range
       // check the target object for @VersionRange annotation with reflection and acquire version ranges, call the function if it is in the desired range


}

একমাত্র সীমাবদ্ধতা হ'ল সকলকে একই নিয়ামকের মধ্যে থাকতে হবে।

এওপি কনফিগারেশনের জন্য http://www.mkyong.com/spring/spring-aop-exferences-advice/ এ একবার দেখুন


ধন্যবাদ হেভি, আমি এটি করার আরও একটি "বসন্ত" বান্ধব উপায় খুঁজছি, কারণ স্প্রিং ইতিমধ্যে এওপি ব্যবহার না করে কোন পদ্ধতিটি কল করতে পছন্দ করে se আমি আমার দৃষ্টিভঙ্গি এওপি কোড জটিলতার একটি নতুন স্তরের যোগ করছি যা আমি এড়াতে চাই।
অগস্টো

@ অগস্টো, স্প্রিংয়ের দুর্দান্ত এওপি সমর্থন রয়েছে। আপনি এটি চেষ্টা করা উচিত। :)
কনস্ট্যান্টিন যোভকভ
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.