#14.1 – serwisy (warstwa pośrednia)

By 13 stycznia 2015Kurs Javy
Wpis-Header lekcje

W tej lekcji dowiemy się, jak dodawać tzw. serwisy – element pośredni pomiędzy bazą danych (naszymi klasami DAO) a widokami (naszymi kontrolerami).

Serwisy to bardzo ważny element każdej aplikacji – stanowią element, dzięki któremu możemy realizować funkcjonalności na różnych typach obiektów, które są w naszej aplikacji, zachowując właściwy podział kodu naszej aplikacji. Brzmi trochę strasznie, wiem, ale jak zobaczysz na przykładach, nie jest to takie straszne :) Ale zaczniemy od odrobiny teorii, żeby lepiej zrozumieć skąd to wynika

Lekcja

Trójwarstwowy podział aplikacji

Obecnie dąży się do tego, żeby budować aplikacje wg podziału na 3 logiczne elementy, pokazane na rysunku obok:

Możemy wyszczególnić trzy warstwy:

  • warstwa persystencji – z punktu widzenia kodu są to wszystkie klasy, które są odpowiedzialne za pobieranie i zapisywanie danych w bazie danych, a więc encje oraz dao; do tej warstwy często zalicza się także samą bazę danych (jak wspominałem, jest to podział wyłącznie logiczny)
  • warstwa usług / logiki biznesowej – na tej warstwie skupimy się dzisiaj, są to klasy, które korzystają z niższej warstwy (persystencji) oraz z innych klas w tej samej warstwie. Taka ‚pozioma’ komunikacja występuje jedynie w tej warstwie (np. jedno DAO nie korzysta z innego DAO, podobnie jak jeden kontroler nie korzysta z innego kontrolera). Jest to więc warstwa łącząca – powinna udostępniać wszystkie operacje, które mogą być realizowane przez kontrolery, a więc wszystko to, co udostępniają klasy DAO a także dodatkowe operacje (np. wysłanie maila, ustawienie powiadomienia itp). Serwisy możemy sobie podzielić dodatkowo na dwie podgrupy (technicznie taki podział nie istnieje – ale dla lepszego zobrazowania sobie sytuacji możemy tak to opisywać):
    • serwisy domenowe – serwisy, które udostępniają operacje związane z konkretnym obiektem domenowym (np. odczyt/zapis do bazy danych, wyszukiwanie w bazie danych, usuwanie, aktualizacja itp)
    • serwisy biznesowe – serwisy, które realizują pewną funkcjonalność, proces biznesowy, integracje z innym systemem itp, dla przykładu weźmy EmailService – serwis, który wysyła maile (przyjmując tylko adres odbiorcy, tytuł i treść, zawiera logikę odpowiedzialną za komunikację z serwerem poczty, logowaniem się do niego, faktycznym wysłaniem maila itp) czy też CurrencyService – serwis, który pozwala obliczyć wartość wyrażoną w innej walucie (wewnętrznie taki serwis będzie korzystał np. z interfejsów udostępnianych przez bank czy też kursów wpisywanych ręcznie przez administratora). Serwisy mogą być używane przez kontrolery (np. formularz kontaktowy wysyła wiadomość mailem do administracji) jak i przez inne serwisy (np. serwis który obsługuje rejestrację użytkowników, wysyła podczas rejestracji maila z prośbą o potwierdzenie)
  • warstwa prezentacji – tutaj mieszczą się zarówno same widoki (pliki JSP) jak też kontrolery. Role kontrolera w aplikacji są dwie:
    • pobranie danych za pośrednictwem serwisów i przygotowanie ich do wyświetlenia użytkownikowi końcowemu (np. zaokrąglając, sumując itp)
    • odebranie danych od użytkownika (np. wysłanych poprzez formularz), walidacja (odsyłam do lekcji o obsłudze formularzy, gdzie uczyliśmy się jak to robić) oraz przekazanie tak przygotowanych danych do serwisów (w których to realizowane są konkretne działania, algorytm, wywoływane są inne systemy itp)

Tworzenie serwisu w Springu

Serwis to nic innego jak klasa, która ma publiczne metody. Aby Spring ‚widział’ naszą klasę jako serwis, dodajemy adnotację @Service . Przykładowy serwis wygląda następująco:

@Service
public class NiepotrzebnyService {
    public String zwrocJedenWyraz() {
        return "jeden";
    }

    public String zwrocDwaWyrazy() {
        return "dwa wyrazy";
    }
}

Aby użyć naszego serwisu w innym serwisie lub w kontrolerze, po prostu dodajemy go jako pole w klasie, w której chcemy użyć, z adnotacją @Autowired :

@Autowired
protected NiepotrzebnyService niepotrzebnyService;

Oczywiście obecnie nie jest on zbyt użyteczny, ale chodzi o pokazanie jak to działa. Kluczowa jest tutaj adnotacja @Service (to jeden z tzw. stereotypów – mówiliśmy o nich krótko w jednej z poprzednich lekcji), która ‚mówi’ Springowi, że jest to serwis, i pozwala nam używać go w innych miejscach (korzystając z pomocy adnotacji @Autowired).

Jako bardziej praktyczny przykład, zaimplementujmy serwis do wysyłki maili.

Piszemy serwis do wysyłania maili

Do wysyłania maili wykorzystamy pakiet javax.mail, musimy więc dodać go najpierw w pliku pom.xml jako dependency (jesli nie pamiętasz jak, zerknij do lekcji o Mavenie). W tym wypadku będziemy potrzebowali dwóch zależności – jedna to API (interfejsy), druga to implementacja (konkretne klasy i kod). Znajdziesz je pod postacią artefaktów: javax.mail:javax.mail-api (interfejsy) oraz com.sun.mail:javax.mail (implementacja).

Oczywiście dane potrzebne do logowania (adres email oraz hasło) musisz podać sama – musi to być działające konto gmail (uwga: ze względu na nie do końca bezpieczny mechanizm wykorzystywany przy logowaniu, konieczne jest  wyłącenie dodatkowych zabezpieczeń!)

Kolejnym krokiem jest stworzenie klasy EmailService z metodą sendEmail, która przyjmie trzy argumenty typu String – email odbiorcy, tytuł wiadomości i treść wiadomości i zwraca wartość typu prawda/fałsz – czy wysyłanie się powiodło. Jej szkielet wygląda nastepująco:

public class EmailService {
    public boolean sendEmail(String recipientEmail, String subject, String content) {
        //logika wysyłania maili
        return false;
    }
}

Następnie implementujemy logikę na podstawie dokumentacji pakietu javax.mail . Poniżej przedstawiam działający kod dla poczty gmail – niestety, jeśli posiadasz konto pocztowe w innym serwisie, ustawienia (szczególnie te związane z bezpieczeństwem) mogą być nieco inne. Wybraliśmy gmail ponieważ można tam bezpłatnie założyć konto, a większość z nas takie konto posiada. Musimy podać dane do naszego konta email, ponieważ będzie ono używane jako nadawca wysyłanych przez nas maili.

@Service
public class EmailService {
	protected String mailSmtpAuth = "true";
	protected String mailSmtpHost = "smtp.gmail.com";
	protected String mailSmtpPort = 587;
	protected String mailSmtpStarttlsEnable = "true";
	protected String mailEmailFrom = "adres@gmail.com";
	protected String username = "adres@gmail.com";
	protected String password = "haslo";

	public boolean sendEmail(String recipientEmail, String subject, String content) {

		Properties props = new Properties();
		props.put("mail.smtp.auth", mailSmtpAuth);
		props.put("mail.smtp.starttls.enable", mailSmtpStarttlsEnable);
		props.put("mail.smtp.host", mailSmtpHost);
		props.put("mail.smtp.port", mailSmtpPort);
                props.put("mail.smtp.ssl.trust", mailSmtpHost);

		Session session = Session.getInstance(props,
		  new javax.mail.Authenticator() {
			protected PasswordAuthentication getPasswordAuthentication() {
				return new PasswordAuthentication(username, password);
			}
		  });

		try {
			Message message = new MimeMessage(session);
			message.setFrom(new InternetAddress(mailEmailFrom));
			message.setRecipients(Message.RecipientType.TO,	InternetAddress.parse(recipientEmail));
			message.setSubject(subject);
			message.setText(content);

			Transport.send(message);
			return true;
		} catch (MessagingException e) {
			//tutaj należy obsłużyć wyjątek - zobacz http://kobietydokodu.pl/niezbednik-juniora-wyjatki-i-ich-obsluga/
		}

		return false;
	}

}

Uwaga! Aby ta metoda działała, musimy także zmienić ustawienia w Gmailu, aby pozwolił nam łączyć się w uproszczony sposób (nie korzystając z całkowicie bezpiecznych mechanizmów). Można to zrobić (po zalogowaniu się) na stronie https://www.google.com/settings/security/lesssecureapps. Pamiętaj, aby po zakończeniu testowania (albo wprowadzeniu danych innego konta) przywrócić to ustawienie do domyślnej wartości!

Podsumowanie

W dzisiejszej lekcji nauczyliśmy się, czym są serwisy i jak je tworzyć w Springu. Tak jak obiecywałem, było prościej niż brzmiało ;) Serwisy to bardzo ważna część aplikacji i jednocześnie taka, którą najtrudniej zautomatyzować – jest ona bowiem związana ściśle z logiką biznesową, a nie standardowymi operacjami. Dlatego warto poświęcić chwilę dłużej i poczytać nieco więcej :)

Materiały dodatkowe / dokumentacja

  1. Opis wzorca Service Layer (EN)

Zadanie

Zmodyfikuj program, który już napisałaś, dodając do niego warstwę serwisów – póki co dla każdego repozytorium (każdego DAO, czyli naszych interfejsów z adnotacją @Repository) utwórz serwis, który oferuje takie same operacje (i kieruje je właśnie do naszego DAO używając go za pomocą pola z adnotacją @Autowired).

Licencja Creative Commons

Jeśli uważasz powyższą lekcję za przydatną, mamy małą prośbę: polub nasz fanpage. Dzięki temu będziesz zawsze na bieżąco z nowymi treściami na blogu ( i oczywiście, z nowymi częściami kursu Javy). Dzięki!

  •  
  •  
  •  
  •  
  •  
  • Kuba

    A gdzie jest miejsce dla serwisów w strukturze projektu Maven? Powinno się dać dodatkowy moduł koty-services, czy każdy serwis ma swój moduł, a może jeszcze inaczej? Pozdrawiam :)

    • Wiele zależy od tego, jak pracuje zespół – nie ma z góry określonej ‚prawidłowej’ struktury projektu Mavena, prawie każda firma czy każdy team ma inną konwencję. Generalnie każdy serwis nie powinien mieć swojego modułu – to zbyt duże rozdrobnienie. Ale nie ma też konieczności umieszczania wszystkich serwisów w jednym module. Nie ma prostej odpowiedzi na postawione pytanie, bo wiele zależy od projektu, zespołu i przyjętych praktyk w firmie

  • Paweł

    Wymagane jest dodatkowy properties:

    props.put(„mail.smtp.ssl.trust”, „smtp.gmail.com”);

    w przeciwnym wypadku sendTextEmail zwróci false, z powodu:

    javax.mail.MessagingException: Could not convert socket to TLS;

    nested exception is:

    javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at com.sun.mail.smtp.SMTPTransport.startTLS(SMTPTransport.java:2046)
    at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:711)
    at javax.mail.Service.connect(Service.java:388)
    ……………………………………………………………….

    • Faktycznie, przeoczyliśmy ten property wcześniej. Dzięki za zwrócenie uwagi!