वसंत के साथ रीस्ट एपीआई संस्करण का प्रबंधन कैसे करें?


118

मैं खोज रहा हूं कि स्प्रिंग 3.2.x का उपयोग करके REST API संस्करणों को कैसे प्रबंधित किया जाए, लेकिन मुझे ऐसा कुछ भी नहीं मिला है जिसे बनाए रखना आसान हो। मैं पहले बताई गई समस्या के बारे में समझाता हूँ, और फिर एक समाधान ... लेकिन मुझे आश्चर्य है कि अगर मैं यहाँ पहिया का पुनः आविष्कार कर रहा हूँ।

मैं स्वीकार शीर्ष लेख के आधार पर संस्करण का प्रबंधन करना चाहता हूं, और उदाहरण के लिए यदि अनुरोध में शीर्षक है 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;
}

इस तरह, मैं एनोटेशन के उत्पादन भाग में परिभाषित बंद या खुली संस्करण श्रेणियां रख सकता हूं। मैं अब इस समाधान पर काम कर रहा हूँ, समस्या यह है कि मैं अभी भी कुछ कोर स्प्रिंग MVC वर्गों को बदलने के लिए किया था (साथ RequestMappingInfoHandlerMapping, RequestMappingHandlerMappingऔर RequestMappingInfo,) है, जो मुझे पसंद नहीं है, क्योंकि यह अतिरिक्त काम का मतलब है जब भी मैं के एक नए संस्करण में नवीनीकृत करने का फैसला वसंत।

मैं किसी भी विचार की सराहना करता हूं ... और विशेष रूप से, किसी भी सुझाव को सरल बनाने के लिए, आसान बनाए रखने के लिए।


संपादित करें

एक इनाम जोड़ना। बाउंटी पाने के लिए, ऊपर दिए गए प्रश्न का उत्तर बिना नियंत्रक के यह तर्क देने के लिए दें। स्प्रिंग के पास पहले से ही बहुत सारे तर्क हैं कि कौन सी कंट्रोलर विधि का चयन करें, और मैं उस पर गुल्लक करना चाहता हूं।


संपादित करें २

मैंने github में मूल POC (कुछ सुधारों के साथ) साझा किया है: https://github.com/augusto/restVersioning



1
@flup मुझे आपकी टिप्पणी समझ नहीं आ रही है। यह सिर्फ यह कहता है कि आप हेडर का उपयोग कर सकते हैं और जैसा कि मैंने कहा, जो बॉक्स से बाहर वसंत प्रदान करता है वह लगातार अद्यतन किए जाने वाले एपीआई का समर्थन करने के लिए पर्याप्त नहीं है। इससे भी बदतर उस उत्तर पर लिंक URL में संस्करण का उपयोग करता है।
अगस्तो

हो सकता है कि वास्तव में आप जो खोज रहे हैं, वह नहीं है, लेकिन स्प्रिंग 3.2 रिक्वेस्ट मैपिंग पर "प्रोडक्शन" पैरामीटर का समर्थन करता है। एक चेतावनी यह है कि संस्करण सूची स्पष्ट होनी चाहिए। जैसे, produces={"application/json-1.0", "application/json-1.1"}इत्यादि
बिम्सापी

1
हमें अपने एपीआई के कई संस्करणों का समर्थन करने की आवश्यकता है, ये अंतर आमतौर पर मामूली बदलाव होते हैं जो कुछ ग्राहकों के कुछ कॉलों को असंगत बना देगा (यह अजीब नहीं होगा अगर हमें 4 मामूली संस्करणों का समर्थन करने की आवश्यकता है, जिसमें कुछ समापन बिंदु असंगत हैं)। मैं इसे url में रखने के सुझाव की सराहना करता हूं, लेकिन हम जानते हैं कि यह गलत दिशा में एक कदम है, क्योंकि हमारे पास URL में संस्करण के साथ कुछ एप्लिकेशन हैं और हर बार हमें बहुत से काम करने पड़ते हैं, जिनसे हमें टक्कर लेनी होगी संस्करण।
अगस्तो

1
@ अगस्त, आप वास्तव में आप भी नहीं हैं। बस अपने एपीआई डिजाइन एक तरीका है कि पिछड़े संगतता को तोड़ने नहीं करता है। बस मुझे परिवर्तनों का उदाहरण दें जो संगतता को तोड़ते हैं और मैं आपको दिखाता हूं कि इन परिवर्तनों को गैर-ब्रेकिंग फैशन में कैसे बनाया जाए।
एलेक्सी एंड्रीव

जवाबों:


62

इसके बावजूद कि क्या वर्जनिंग को पिछड़े संगत बदलावों से बचा जा सकता है (जो हमेशा संभव नहीं हो सकता है जब आप कुछ कॉरपोरेट दिशानिर्देशों से बंधे हों या आपके एपीआई क्लाइंट एक छोटी गाड़ी में लागू किए गए हों और नहीं तो भी टूटेंगे) अमूर्त आवश्यकता एक दिलचस्प है एक:

मैं एक कस्टम अनुरोध मानचित्रण कैसे कर सकता हूं जो विधि निकाय में मूल्यांकन किए बिना अनुरोध से हेडर मानों का मनमाना मूल्यांकन करता है?

जैसा कि इस एसओ उत्तर में वर्णित है, आप वास्तव में एक ही हो सकते हैं @RequestMappingऔर रनिंग के दौरान होने वाली वास्तविक रूटिंग के दौरान अंतर करने के लिए एक अलग एनोटेशन का उपयोग कर सकते हैं । ऐसा करने के लिए, आपको निम्न करना होगा:

  1. एक नया एनोटेशन बनाएं VersionRange
  2. लागू करें ए RequestCondition<VersionRange>। चूंकि आपके पास एक सर्वश्रेष्ठ-मैच एल्गोरिथ्म जैसा कुछ होगा, आपको यह जांचना होगा कि क्या अन्य VersionRangeमूल्यों के साथ एनोटेट किए गए तरीके वर्तमान अनुरोध के लिए एक बेहतर मैच प्रदान करते हैं।
  3. VersionRangeRequestMappingHandlerMappingएनोटेशन और अनुरोध की स्थिति के आधार पर लागू करें (जैसा कि पोस्ट में वर्णित है कि @RequestMapping कस्टम गुण कैसे लागू करें )।
  4. VersionRangeRequestMappingHandlerMappingडिफ़ॉल्ट का उपयोग करने से पहले अपने मूल्यांकन के लिए वसंत को कॉन्फ़िगर करें RequestMappingHandlerMapping(जैसे इसके क्रम को 0 पर सेट करके)।

इसके लिए स्प्रिंग कंपोनेंट्स के किसी भी हैप्पी रिप्लेसमेंट की आवश्यकता नहीं होगी, लेकिन स्प्रिंग कॉन्फ़िगरेशन और एक्सटेंशन मैकेनिज़्म का उपयोग करता है, तो यह तब भी काम करना चाहिए जब आप अपना स्प्रिंग वर्जन अपडेट कर दें (जब तक नया वर्जन इन मैकेनिज़्म को सपोर्ट नहीं करता)।


उत्तर xwoker के रूप में अपनी टिप्पणी जोड़ने के लिए धन्यवाद। अब तक का सबसे अच्छा है। मैंने आपके द्वारा बताए गए लिंक के आधार पर समाधान लागू किया है और यह उतना बुरा नहीं है। स्प्रिंग के एक नए संस्करण में अपग्रेड करते समय सबसे बड़ी समस्या सामने आएगी क्योंकि इसके पीछे तर्क में किसी भी बदलाव की जांच करने की आवश्यकता होगी mvc:annotation-driven। उम्मीद है कि स्प्रिंग एक संस्करण प्रदान करेगा mvc:annotation-drivenजिसमें कोई कस्टम शर्तों को परिभाषित कर सकता है।
अगस्तो

@ ऑगस्टो, आधे साल बाद, यह आपके लिए कैसे काम कर रहा है? इसके अलावा, मैं उत्सुक हूं, क्या आप वास्तव में प्रति-पद्धति के आधार पर संस्करण बना रहे हैं? इस बिंदु पर मैं सोच रहा हूं कि क्या यह प्रति-वर्ग / प्रति-नियंत्रक स्तर की ग्रैन्युलैरिटी पर संस्करण के लिए स्पष्ट नहीं होगा?
Sander Verhagen

1
@SanderVerhagen यह काम कर रहा है, लेकिन हम पूरे API का संस्करण करते हैं, विधि या नियंत्रक के अनुसार नहीं (व्यवसाय के एक पहलू पर ध्यान केंद्रित करते हुए एपीआई काफी छोटा है)। हमारे पास एक बहुत बड़ी परियोजना है, जहाँ उन्होंने प्रति संसाधन एक भिन्न संस्करण का उपयोग करने का विकल्प चुना है और यह निर्दिष्ट किया है कि URL पर (इसलिए आपके पास / v1 / सत्रों पर एक समापन बिंदु हो सकता है और एक अन्य संसाधन पूरी तरह से भिन्न संस्करण, जैसे / v4 / आदेशों पर) ... यह थोड़ा अधिक लचीला है, लेकिन यह ग्राहकों पर यह जानने के लिए अधिक दबाव डालता है कि प्रत्येक समापन बिंदु के किस संस्करण को कॉल किया जाए।
अगस्तो

1
दुर्भाग्य से, यह Swagger के साथ अच्छा नहीं खेलता है, क्योंकि WebMvcConfigurationSupport का विस्तार करते समय बहुत सारे ऑटो कॉन्फ़िगरेशन बंद हो जाते हैं।
रिक

मैंने इस समाधान की कोशिश की लेकिन वास्तव में 2.3.2 के साथ काम नहीं कर रहा हूँ। कृपया। क्या आपके पास दिखाने के लिए कुछ उदाहरण परियोजना है?
पैट्रिक

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);
    }

}

WebMvcConfigurationSupport में इंजेक्शन:

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

4
मैंने "1.2" जैसे संस्करणों की अनुमति देने के लिए int [] को स्ट्रिंग [] में बदल दिया, और इसलिए मैं "नवीनतम" जैसे कीवर्ड संभाल सकता हूं
Maelig

3
हाँ, यह बहुत उचित है। भविष्य की परियोजनाओं के लिए, मैं कुछ कारणों से एक अलग रास्ता तय करूँगा: 1. URL संसाधनों का प्रतिनिधित्व करते हैं। /v1/aResourceऔर /v2/aResourceविभिन्न संसाधनों की तरह देखो, लेकिन यह एक ही संसाधन का एक अलग प्रतिनिधित्व है! 2. HTTP हेडर का उपयोग करना बेहतर लगता है, लेकिन आप किसी को URL नहीं दे सकते, क्योंकि URL में हेडर नहीं है। 3. एक URL पैरामीटर का उपयोग करना, यानी /aResource?v=2.1(btw: यह वह तरीका है जिससे Google संस्करण करता है)। ...मुझे अभी भी यकीन नहीं है कि मैं विकल्प 2 या 3 के साथ जाऊंगा , लेकिन मैं ऊपर बताए गए कारणों के लिए फिर से 1 का उपयोग नहीं करूंगा ।
बेंजामिन एम

5
यदि आप अपने आप RequestMappingHandlerMappingको अपने में इंजेक्ट करना चाहते हैं WebMvcConfiguration, तो आपको createRequestMappingHandlerMappingइसके बजाय ओवरराइट करना चाहिए requestMappingHandlerMapping! अन्यथा आप अजीब समस्याओं का सामना करेंगे (मुझे अचानक बंद सत्र की वजह से हाइबरनेट्स आलसी
आरोपण की

1
दृष्टिकोण अच्छा लग रहा है लेकिन किसी तरह लगता है कि यह जून्टी टेस्ट मामलों (स्प्रिंगरनर) के साथ काम नहीं करता है। कोई भी मौका जो आपको परीक्षण मामलों के साथ काम करने का दृष्टिकोण मिला है
JDev

1
इस काम को करने का एक तरीका है, विस्तार नहीं WebMvcConfigurationSupport बल्कि विस्तार DelegatingWebMvcConfiguration। इसने मेरे लिए काम किया (देखें stackoverflow.com/questions/22267191/… )
SeB.Fr

17

मैं अभी भी URL के संस्करण के लिए उपयोग करने की सलाह दूंगा क्योंकि URL में @RequestMapping पैटर्न और पथ मापदंडों का समर्थन करता है, जो प्रारूप regexp के साथ निर्दिष्ट किया जा सकता है।

और क्लाइंट अपग्रेड (जिसे आपने टिप्पणी में उल्लेख किया है) को संभालने के लिए आप 'नवीनतम' जैसे उपनामों का उपयोग कर सकते हैं। या फिर एपी का असम्बद्ध संस्करण है जो नवीनतम संस्करण (हाँ) का उपयोग करता है।

पथ पैरामीटर का उपयोग करके आप किसी भी जटिल संस्करण हैंडलिंग तर्क को लागू कर सकते हैं, और यदि आप पहले से ही सीमाएं चाहते हैं, तो आप बहुत अच्छी तरह से जल्द ही कुछ और चाहते हैं।

यहाँ कुछ उदाहरण हैं:

@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;
    }
}

अंतिम दृष्टिकोण के आधार पर आप वास्तव में कुछ वैसा ही लागू कर सकते हैं जैसा आप चाहते हैं।

उदाहरण के लिए आपके पास एक नियंत्रक हो सकता है जिसमें संस्करण हैंडलिंग के साथ केवल विधि स्टैब्स शामिल हैं।

उस हैंडलिंग में आप कुछ वसंत सेवा / घटक में या एक ही नाम / हस्ताक्षर के साथ विधि के लिए एक ही कक्षा में (प्रतिबिंब / एओपी / कोड पीढ़ी पुस्तकालयों का उपयोग करके) देखते हैं और @VersionRange की आवश्यकता होती है और इसे सभी मापदंडों को पारित करने के लिए आमंत्रित करते हैं।


14

मैंने एक समाधान लागू किया है जो सही ढंग से संभालता है बाकी संस्करण के साथ से समस्या को ।

सामान्य बोलना बाकी संस्करण के लिए 3 प्रमुख दृष्टिकोण हैं:

  • पथ- आधारित दृष्टिकोण, जिसमें क्लाइंट URL में संस्करण को परिभाषित करता है:

    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 )

समाधान

चूंकि मैं बाकी दस्तावेज़ीकरण टूल के साथ बहुत काम कर रहा हूं, इसलिए मैं पहले दृष्टिकोण का उपयोग करना पसंद करता हूं। मेरा समाधान पहले दृष्टिकोण के साथ समस्या को संभालता है , इसलिए आपको समापन बिंदु को नए संस्करण में कॉपी-पेस्ट करने की आवश्यकता नहीं है।

मान लें कि हमारे पास उपयोगकर्ता नियंत्रक के लिए v1 और v2 संस्करण हैं:

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" प्रतिनिधि लेना है, अन्यथा अगर मैं v2 , v3 और इतने पर अनुरोध करता हूं तो मुझे "उपयोगकर्ता V2" की प्रतिक्रिया लेनी होगी ।

यहां छवि विवरण दर्ज करें

वसंत में इसे लागू करने के लिए, हमें डिफ़ॉल्ट RequestMappingHandlerMapping व्यवहार को ओवरराइड करना होगा :

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 को हल करने के लिए वसंत से पूछता है। यदि यह URL मौजूद नहीं है (उदाहरण के लिए क्लाइंट ने v3 का अनुरोध किया है ) तो हम इसके साथ प्रयास करते हैं v2 के और इसलिए जब तक हमें संसाधन के लिए सबसे हाल का संस्करण नहीं मिल जाता है ।

इस कार्यान्वयन से लाभ देखने के लिए, मान लें कि हमारे पास दो संसाधन हैं: उपयोगकर्ता और कंपनी:

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

मान लीजिए कि हमने ग्राहक को तोड़ने वाली कंपनी "अनुबंध" में बदलाव किया है। इसलिए हम लागू करते हैंhttp://localhost:9001/api/v2/company और हम ग्राहक से v1 के बजाय v2 में बदलने के लिए कहते हैं।

तो ग्राहक से नए अनुरोध हैं:

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

के बजाय:

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

यहाँ सबसे अच्छी बात यह है कि इस समाधान से ग्राहक को v2 से उपयोगकर्ता की जानकारी और v2 से कंपनी की जानकारी प्राप्त होगी, उपयोगकर्ता v2 से एक नया (समान) समापन बिंदु बनाने की आवश्यकता के बिना !

डॉक्यूमेंटेशन को फिर से करें जैसा कि मैंने कारण बताया कि इससे पहले कि मैं यूआरएल-आधारित वर्जनिंग दृष्टिकोण का चयन करता हूं, स्वैगर जैसे कुछ उपकरण एक ही URL के साथ अलग-अलग एंडपॉइंट्स के लिए अलग-अलग दस्तावेज़ नहीं बनाते हैं। इस समाधान के साथ, दोनों समापन बिंदु अलग-अलग URL प्रदर्शित होते हैं:

यहां छवि विवरण दर्ज करें

GIT

समाधान कार्यान्वयन यहां: 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काम करता है
अगस्तो

@ ऑगस्टो आपको ऐसा करने की आवश्यकता नहीं है। इस दृष्टिकोण के साथ, आप "एपीआई" नहीं बल्कि "एंडपॉइंट" का संस्करण तैयार कर रहे हैं। प्रत्येक समापन बिंदु का एक अलग संस्करण हो सकता है। मेरे लिए यह एपीआई संस्करण की तुलना में एपीआई संस्करण का सबसे आसान तरीका है । स्वैगर भी सेटअप करने के लिए अधिक सरल है । इस रणनीति को सामग्री बातचीत के माध्यम से संस्करण कहा जाता है।
धरिक

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। हालांकि, विरासत में कसकर जोड़े हैं। बजाय। कंट्रोलर (टेस्ट 1 और टेस्ट 2) सिर्फ एक पास होना चाहिए ... कोई तर्क नहीं। सब कुछ तर्क सेवा वर्ग, कुछ सेवा में होना चाहिए। उस मामले में, बस सरल संरचना का उपयोग करें और अन्य नियंत्रक से कभी भी विरासत में न लें
दान Hunex

1
@ दान-हनेक्स ऐसा लगता है कि केके अपी के विभिन्न संस्करणों का प्रबंधन करने के लिए विरासत का उपयोग करते हैं। यदि आप विरासत को हटाते हैं, तो समाधान क्या है? और क्यों कसकर जोड़े इस उदाहरण में एक समस्या है? मेरे दृष्टिकोण से, Test2 Test1 का विस्तार करता है क्योंकि यह इसका एक सुधार है (समान भूमिका और समान जिम्मेदारियों के साथ), क्या यह नहीं है?
जेरीमेका

2

मैंने पहले ही URI संस्करण का उपयोग करके अपना API संस्करणित करने का प्रयास किया , जैसे:

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

लेकिन इस काम को करने की कोशिश करते समय कुछ चुनौतियां हैं: विभिन्न संस्करणों के साथ अपने कोड को कैसे व्यवस्थित करें? एक ही समय में दो (या अधिक) संस्करण कैसे प्रबंधित करें? कुछ संस्करण निकालते समय क्या प्रभाव पड़ता है?

सबसे अच्छा विकल्प जो मुझे मिला वह संपूर्ण API का संस्करण नहीं था, बल्कि प्रत्येक समापन बिंदु पर संस्करण को नियंत्रित करता था । इस पैटर्न को कंटेंट बातचीत के माध्यम से एक्सेप्ट हेडर या वर्जनिंग का उपयोग करके वर्जनिंग कहा जाता है :

यह दृष्टिकोण हमें संपूर्ण API को संस्करणित करने के बजाय एकल संसाधन प्रतिनिधित्व को संस्करणित करने की अनुमति देता है जो हमें संस्करणकरण पर अधिक दानेदार नियंत्रण प्रदान करता है। यह कोड आधार में एक छोटा पदचिह्न भी बनाता है क्योंकि हमें नया संस्करण बनाते समय पूरे आवेदन को कांटा नहीं जाता है। इस दृष्टिकोण का एक और लाभ यह है कि इसे URI पथ के माध्यम से संस्करण द्वारा शुरू किए गए URI रूटिंग नियमों को लागू करने की आवश्यकता नहीं है।

वसंत पर कार्यान्वयन

सबसे पहले, आप एक मूलभूत उत्पादन विशेषता के साथ एक नियंत्रक बनाते हैं, जो कि कक्षा के अंदर प्रत्येक समापन बिंदु के लिए डिफ़ॉल्ट रूप से लागू होगा।

@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);
}

किया हुआ! बस वांछित Http हैडर संस्करण का उपयोग करके प्रत्येक समापन बिंदु को कॉल करें :

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

या, संस्करण दो को कॉल करने के लिए:

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

अपनी चिंताओं के बारे में:

और चूंकि एपीआई के सभी तरीके एक ही रिलीज में नहीं बदलते हैं, इसलिए मैं अपने प्रत्येक कंट्रोलर के पास नहीं जाना चाहता हूं और हैंडलर के लिए कुछ भी बदलना चाहता हूं जो संस्करणों के बीच नहीं बदला है

जैसा कि समझाया गया है, यह रणनीति प्रत्येक नियंत्रक को उसके वास्तविक संस्करण के साथ बनाए रखती है। आप केवल उस समापन बिंदु को संशोधित करते हैं जिसमें संशोधन और एक नया संस्करण है।

और स्वैगर?

विभिन्न संस्करणों के साथ स्वैगर को सेट करना भी इस रणनीति का उपयोग करना बहुत आसान है। अधिक विवरण के लिए यह उत्तर देखें।


1

उत्पादन में आप नकारात्मक हो सकते हैं। तो Method1 के लिए कहते हैंproduces="!...1.7" Method1 के और मेथड 2 में पॉजिटिव है।

उत्पादन भी एक सरणी है जिससे आप method1 के लिए कह सकते हैं produces={"...1.6","!...1.7","...1.8"}आदि ( आप 1.7 को छोड़कर सभी स्वीकार करें)

संभोग के रूप में आदर्श के रूप में पर्वतमाला है कि आप मन में है, लेकिन मुझे लगता है कि अन्य कस्टम सामान की तुलना में बनाए रखना आसान है अगर यह आपके सिस्टम में कुछ असामान्य है। सौभाग्य!


धन्यवाद codealsa, मैं एक ऐसा तरीका खोजने की कोशिश कर रहा हूं जो बनाए रखना आसान है और हर बार जब हमें संस्करण को टक्कर देने की आवश्यकता होती है, तो प्रत्येक समापन बिंदु को अपडेट करने के लिए कुछ की आवश्यकता नहीं होती है।
अगस्तो

0

आप अवरोधन के आसपास, AOP का उपयोग कर सकते हैं

एक अनुरोध मानचित्रण पर विचार करें जो सभी प्राप्त करता है /**/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


}

एकमात्र बाधा यह है कि सभी को एक ही नियंत्रक में होना चाहिए।

AOP कॉन्फ़िगरेशन के लिए http://www.mkyong.com/spring/spring-aop-examples-advice/ पर एक नज़र


धन्यवाद हेवी, मैं इसे करने के लिए एक और अधिक "वसंत" के अनुकूल तरीके की तलाश कर रहा हूं, क्योंकि वसंत पहले से ही एओपी का उपयोग किए बिना किस विधि का चयन करता है। मेरा विचार है कि AOP कोड जटिलता का एक नया स्तर जोड़ता है जिससे मैं बचना चाहूंगा।
अगस्तो

@ ऑगस्टो, स्प्रिंग में एक महान एओपी समर्थन है। आपको इसे आज़माना चाहिए। :)
कॉन्स्टेंटिन योकोव
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.