Miałem ostatnio okazję implementować własny provider claimów do SharePointa. Jak prawie całe API SharePointa, ta kupa kupy woła o pomstę do nieba. Postanowiłem wykorzystać okazję i zaprezentować jak to złe API mogłoby wyglądać, gdyby ktoś w teamie odpowiedzialnym za “dev-experience” tego kolosa miał trochę oleju w głowie.
Ale najpierw kilka słów wprowadzenia, czyli przedstawienie problemu. Dla szczęśliwców nieobeznanych z SharePointem małe screenshoty:
Na powyższym obrazku widzimy ekran do edycji “roli”. Nie musi to być rola w rozumieniu “role-based security”, tutaj nie ma to znaczenia. Znaczenie ma jedynie pole “Role Members” – ten kawałek UI odpowiedzialny jest za skojarzenie listy użytkowników z jakimś elementem (tym elementem jest tutaj rola właśnie). W pole można wpisać cokolwiek, a po kliknięciu ikonki “ludzika z checkboxem” następuje proces “name resolution” – jeśli wpisany tekst da się powiązać z użytkownikiem istniejącym w systemie, to zostanie on podkreślony i będzie traktowany jako jedna całość. W przeciwnym wypadku zobaczymy komunikat o błędzie mówiący, że nie można “zresolwować tekstu i wywnioskować z niego o jakiego usera chodzi”.
Zamiast wklepywać ręcznie nazwę użytkownika możemy posłużyć się dodatkowym mechanizmem – wyszukiwarką. Otwiera się ona po kliknięciu książeczki i wygląda tak:
Ekran podzielony jest na trzy obszary: pole wyszukiwania, drzewko oraz listę wyników wyszukiwania.
Dość o UI.
Własna implementacja claims providera może być odpowiedzialna za cztery rzeczy:
- nadawanie dodatkowych claimów użytkownikom logującym się do systemu (tego nie widać we wklejonym UI)
- “name resolution” – pierwszy screen
- budowanie hierarchii – drzewko na drugim screenie
- wyszukiwanie – lista wyników na drugim screenie
Jakby tego było mało, możemy sobie wybrać co obsługujemy, więc jedna implementacja providera może dorzucać claimy a całkiem inna odpowiadać za wyszukiwanie obiektów.
Czy ma to sens? “Sharepoint developers” powiedzą, że tak. Ale oni pewnie rzadko poza SP wyglądają.
A cała reszta prawdopodobnie za głowę się złapie i zgodnie wskaże jaką zasadę (niejedną zresztą) brutalnie tutaj złamano.
Efektem takiej decyzji jest to, że abstrakcyjna klasa, z której dziedziczę, ma pierdylion metod abstrakcyjnych. Jeśli nie chcę aby mój provider wspierał któryś scenariusz to zostawiam je puste.
Ale skąd SharePoint będzie wiedział które funkcjonalności wspiera moja implementacja? Ano nawet przedszkolak by potrafił takie coś zaprojektować, przecież wystarczy dodać cztery właściwości bool:
- SupportsEntityInformation
- SupportsHierarchy
- SupportsResolve
- SupportsSearch
Fuck yeah!!! Mało tego – SharePoint i tak wywołuje metody, które powyższymi flagami oznaczam jako niewspierane. Żal.pl.
Czytałem sobie o tym na MSDNie, obejrzałem wideło na Pluralsight i… zero komentarza na ten temat. Po prostu przyjęte jest za pewnik, że tak powinno być. I koniec. Nikt nie powie, że to nie ma sensu, ponieważ te funkcjonalności mają ze sobą tyle wspólnego co romantyczna randka i psy walące się na ulicy w pierwszy dzień wiosny. Po prostu poszczególne części klasy dziedziczącej są pozwijane w odpowiednio nazwane regiony i jazda! “MS tak chciał”.
Niewiele na to da się poradzić. Ja starałem się zachować choć trochę porządku w kodzie i (jak pisałem w poście o regionach) rozbiłem implementację na 5 plików partial:
Nie jest idealnie, ale… trochę lepiej.
Ale jak to można było zaprojektować inaczej? Tu również odpowiedź jest prosta. Zamiast narzucać obowiązek implementowania niepotrzebnych metod i sterowania “możliwościami” providera za pomocą durnych flag wystarczyło rozbić funkcjonalności pomiędzy cztery interfejsy. IClaimsAugmentation, IHierarchyProvider, INameResolver, IClaimsEntitySearch… czy jakkolwiek inaczej je tam nazwać można. Nie jedna wielka klasa abstrakcyjna, która robi wszystko. Każdy z tych interfejsów i tak wymaga całkowicie odrębnych metod i wrzucenie ich do jednego wora nie mieści się po prostu w głowie. Przed SharePointem stałoby wówczas banalne zadanie: rzutuj mój provider na interfejs numer jeden. Nie null? Wywołuj metody. Potem rzutuj na interfejs numer dwa… I tak po kolei. Albo po prostu SP mógłby przeszukać assemblies w poszukiwaniu implementacji interfejsów…
Czasami ciężko jest mi zrozumieć skąd takie wynalazki się w ogóle biorą. I to głównie właśnie w produktach z Redmond. Sharepoint, Forefront Identity Manager, Gutek pewnie potwierdzi że podobnie jest w CRM… A niby tam biorą tylko najlepszych.
Jak nie budować API – na przykładzie SPClaimProvider | Maciej Aniserowicz o programowaniu…
Dziękujemy za dodanie artykułu – Trackback z dotnetomaniak.pl…
Na podstawie SharePointa można napisać całą książkę o tym, jak nie konstruować API…
jakubin,
… a kto może o tym wiedzieć lepiej niż skład teamów MOSSters, KateMOSS i 2 innych których nazw nie pamiętam …? ;)
Kilka(naście) lat temu sam tak pisałem… Rzucony na głęboką wodę i brak mistrza, który powie: Nie tędy droga chłopcze.
Tak wszedzie w produktach MSowych jest (na przynajmniej tych serwerowych) ;) ale co poczac, sam tez bylem zmuszony robic takie cuda swojego czasu, chociaz musze powiedziec ze wtedy query byl w osobnej klasie i nazywal sie SimpleQueryControl, wiec i burdelu bylo troche mniej.
dlatego ucieklem od tego SharePointa swojego czasu i teraz tylko dorywczo cos tam naskrobie :)
No nie mogą tego security rozgryźć. IMHO już MembershipProvider to był przerost formy, ale tutaj widzę poszli na całość. Czasem dobrze mieć ograniczenia (deadliny, kasa), bo gdyby nie one sam bym takie mini WCFy popełniał. Choć może na odwrót – właśnie w pośpiechu to robili? I za Pascalem “gdybym miał więcej czasu, napisałbym Ci krótszy list”. BTW dość ciekawa prezentacja “How to Design a Good API and Why it Matters”: http://lcsd05.cs.tamu.edu/slides/keynote.pdf
Odnoszę wrażenie że poznawanie różnych API dość często sprowadza się do znanego z j. polskiego w liceum zadania “co autor miał na myśli” i szukania wyjaśnienia poza decyzjami projektowymi. Podejrzewam, że taki design pojawił się dawno temu, a później – żeby nie zmieniać założeń API i przyzwyczajeń programistów – pozostawiono go jak jest, przenosząc do kolejnych wersji.
Z API Symbiana była (jest?) podobna historia – znaczna część założeń, koncepcji i projektu API (m.in. deskryptory, komunikacja między aplikacjami, mechanizm wywołań asynchronicznych, obsługa błędów) to wynik naleciałości sprzed kilku-kilkunastu lat, gdy urządzenia miały po kilka MB RAM i ARM kilkanaście MHz jako procesor, a aplikacje były DLLkami ładowanymi przez proces systemu/UI. Poznanie historii platformy było niezbędne, żeby zrozumieć o co w tym wszystkim chodzi. Może w przypadku SP jest podobnie, taka konstrukcja to naleciałość z np. czasów COMowskich?
Zapewne czegoś nie rozumiem (nie miałem (nie)szczęścia obeznać się z SharePointem) ale nie można by napisać wrappera, przyjmującego Nowego, Lepszego Providera, rzutującego go na interfejsy itd, itd (jak opisane w poście), zajmującego się całym tym bałaganem i służącego jako claim provider dla SharePointa?
Fakt, wyjdzie trochę więcej kodu, ale implementacja czegoś takiego składałaby się głównie z warunkowego przekazywania wywoływancyh przez SP abstrakcyjnych funkcji dalej, czyli nie byłaby zbyt bolesna. A przy odrobinie szczęścia może nawet ktoś już coś takiego napisał.
Trzeba było nie narzekać tylko rzeczywiście obsłużyć te cztery interfejsy. Jedna warstwa więcej. 4 protected virtual GetCośTam albo jeden GetServiceProvider i bez partiali by sie obeszło.
MSM, Arek,
Oczywiście że można było to zrobić. I jeśli musiałbym się w tym grzebać więcej to bym tak zrobił (zrobiłem dla FIM, który ma równie idiotyczny mechanizm komunikacji z zewnętrznymi bibliotekami). Tu nie było sensu, a pokazanie problemu i potencjalnej alternatywy wydało mi się fajnym pomysłem.
Ja również uczestniczę w projekcie w którym zbudowałem własnego Claim Providera dziedzicząc po tej klasie abstrakcyjnej SPClaimProvider na potrzeby PeoplePicker, gdzie użytkownicy są wyszukiwani w zewnętrznym systemie. Dodatkowo ten własny provider jest traktowany jako provider do lokalizowania użytkowników w napisanym tzw. Trusted Login Provider służącym do logowania użytkowników SAML. Generalnie również zauważyłem ten burdel o którym piszesz co to ta klasa bazowa nie potrafi ;/ ale na potrzeby szybkiego rozwiązania tematu trzeba było się dopasować do takiego podejścia….