Coraz więcej osób zgłasza się do mnie poszukując pomocy w przejściu ze stanowiska Junior Java Developer na Mid-Level Java Developer. Chcąc pomóc jak największej liczbie osób w tym artykule przedstawię najczęściej zadawane mi pytania oraz moje podejście do tematu przejścia z Junior na Mid-Level Java Developer.
Część osób, które zgłaszają się do mnie czują, że stoją w miejscu, nie rozwijają się, to ich motywuje do zmiany. Spora część osób zgłasza się do mnie, co jest smutne, ponieważ nie otrzymuje właściwego wsparcia w projekcie od doświadczonych programistów. Dobra informacja jest taka, że każda z tych osób może przejść od junior do mid-level.
Pomijając kwestie nazewnictwa Junior vs. Mid-Level, to należy pamiętać, że w tym wszystkim chodzi o doskonalenie warsztatu programistycznego. Dodatkowo każda firma inaczej nazywa stanowiska, np.: Młodszy Specjalista ds. Rozwoju Oprogramowania, Junior Java Developer, Software Engineer. Dlatego w tym artykule duży nacisk położę na kwestię doskonalenia.
Najczęściej zadawane pytania:
Po ilu latach mogę zostać Mid-Level Java Developerem, Developerką?
Jaki zakres wiedzy muszę posiadać?
Ile projektów muszę zrealizować?
Jak uzyskać wsparcie w projekcie od doświadczonych programistów?
Po ilu latach mogę zostać Mid-Level Java Developerem, Developerką?Granica przejścia z Junior na Mid-Level Java Developer jest bardzo umowna. Moim zdaniem nie zależy ona od stażu pracy w latach, najważniejsza jest „ilość” oraz różnorodność projektów, w których Junior Java Developer, Developerka brali udział.
Pytania „Jaki zakres wiedzy muszę posiadać?” oraz „Ile projektów muszę zrealizować?” zamieniłbym na jedno zasadnicze pytanie: Jak zapewnić sobie różnorodność projektów w firmie? Zacznijmy od tego, że różne projekty pozwalają na pracę z wieloma technologiami i narzędziami. Najważniejsze, to nie zamykać się we własnej strefie komfortu, działając w dobrze nam znanym i bezpiecznym projekcie.
Różnorodność można znaleźć nawet w obrębie jednego projektu, implementując jego różne moduły/funkcjonalności. Pracując przy np.: module przelewów bankowych można „przejść” do modułu odpowiedzialnego za generowanie i/lub archiwizację dokumentów bankowych – potwierdzenie przelewu, wyciąg z karty kredytowej.
Zazwyczaj projekty wchodzą w skład większego systemu informatycznego, każdy projekt jest realizowany przez inny dział w firmie, to stwarza możliwość zmiany projektu w ramach różnych działów. Pracując przy np. projekcie billingowym dla operatora telekomunikacyjnego można przejść do działu wsparcia sprzedaży urządzeń.
Jak uzyskać wsparcie w projekcie od doświadczonych programistów? Zakładając, że wychodzimy z pozycji Junior Java Developera, Developerki z kilkunastoma miesiącami doświadczenia oraz ze znajomością elementów opisanych w Jak powinny wyglądać realne wymagania dla Junior Java Developer’a? – Just Join IT – Java, git, Maven/Gradle, JUnit5, HTTP, REST, Spring Framework, Hibernate ORM.
Temat uzyskania wsparcia od doświadczonych programistów jest dość złożony, bo chodzi tu o interakcję z drugim człowiekiem. Mogę podać jeden sprawdzony sposób. Dla tworzonego kodu – umieszczanego na nowym branchu w git – tworzymy Pull Request/Megre Request, przypisując doświadczonych kolegów jako osoby do review – przegląd, weryfikacja naszego kodu. Można również wykorzystać (w granicach rozsądku) wszystkie dostępne środki komunikacji, np.: chat, video, telefon oraz rozmowa w cztery oczy.
Moje podejście do tematu wsparcia przejścia z Junior do Mid-Level Java Developer.
Część prezentowanego kodu źródłowego w samym artykule może być w wersji „okrojonej”, tak, aby przedstawić istotę zagadnienia bez zaciemniania obrazu szczegółami. Cały kod źródłowy jest na GitHub.
Dogłębne poznanie wykorzystywanych frameworków oraz narzędzi pozwala pokazać jak kiedyś wyglądało „programowanie”. To pozwala zobaczyć jak wiele ułatwień dają współczesne rozwiązania. Pozwala również lepiej zrozumieć dlaczego coś działa tak, a nie inaczej oraz jak lepiej wykorzystać technologię i rozwiązać pojawiające się błędy.
Spring MVC wykorzystuje Java Servlet, technologia Servletów ma 26 lat – Jakarta Servlet – wchodzi w skład Java EE – Java EE, to obecnie Jakarta EE po “oddelegowaniu projektu” przez firmę Oracle do Eclipse Foundation. Używając Java/Jakarta EE trzeba „zainstalować” serwer webowy obsługujący protokuł HTTP – Apache Tomcat – skonfigurować go, dodać plugin do Maven i już można uruchamiać aplikacje web. Korzystając ze Spring MVC + Spring Boot wystarczy wygenerować aplikację web – Spring Initializr – następnie po prostu uruchomić aplikację.
Poniższy kod pokazuje jak obsłużyć żądanie protokołu HTTP dla metody GET wraz z przesłaniem parametru żądania. Dla technologi Java/Jakarta EE potrzebujemy klasy Java NotThatSimpleJavaServlet oraz pliku web.xml z konfiguracją. Dla Spring MVC wystarczy jedna klasa Java SimpleSpringController. W przypadku Java/Jakarta Servlet niepotrzebnie wiążemy się z klasą HttpServlet – poprzez dziedziczenie, extends – jak wiemy w języku Java nie ma wielodziedziczenia, obsługa wyjątków jest utrudniona.
Obsługa end-point, np.: http://localhost:8080/dashboard/java/servlet/ – połączenie klasy Java NotThatSimpleJavaServlet oraz pliku konfiguracyjnego web.xml.
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping(value = "/dashboard/simple/spring")
public class SimpleSpringController {
@GetMapping
public String dashboardView(String dashboardMode, Model model) {
if (dashboardMode != null) {
model.addAttribute("mode", dashboardMode);
}
return "dashboard.html";
}
}
Obsługa end-point, np.: http://localhost:8080/dashboard/simple/spring/ – wymaga tylko jednej klasy Java SimpleSpringController – konfiguracja dostarczona przez Spring Boot.
Hibernate wykorzystuje JDBC (Java Database Connectivity), co oznacza, że warto wiedzieć, co dzieje się „pod spodem”. Poznanie czystego JDBC wymusza również znajomość SQL, co jest bardzo ważnym elementem w warsztacie Mid-Level Java Developera, Developerki. Dodatkowo, jeżeli nam na wydajności operacji bazodanowych, to JDBC jest bardzo dobrym rozwiązaniem. Należy pamiętać, że mamy więcej możliwości utrwalania danych w Java – więcej szczegółów w artykule https://dev.to/yigi/hibernate-vs-jdbc-vs-jpa-vs-spring-data-jpa-1421. Kod źródłowy z przykładami wykorzystania Hibernate można znaleźć na moim GitHub – https://github.com/juniorjavadeveloper-pl/hibernate-examples/.
CREATE TABLE EMPLOYEES(
ID INT PRIMARY KEY,
FIRST_NAME VARCHAR(255),
LAST_NAME VARCHAR(255),
DEPARTMENT VARCHAR(255)
);
Korzystając z JDBC należy samemu utworzyć elementy w bazie danych, np.: tabele. Powyższy skrypt SQL tworzy tabelę o nazwie EMPLOYEES.
public class EmployeeModel {
private Long id;
private String firstName;
private String lastName;
private String department;
// getters/setters
}
Potrzebujemy również klasę POJO, która będzie odpowiadać strukturze tabeli w bazie danych, ale klasę POJO musimy samodzielnie wypełnić za pomocą klasy DAO – w odróżnieniu od Hibernate, gdzie dzieje się, to automatycznie.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class EmployeeModelDao {
private static final String DB_URL =
"jdbc:h2:~/h2database/hibernate-orm-jdbc-database";
private static final String INSERT_EMPLOYEE_SQL =
"INSERT INTO EMPLOYEES(ID, FIRST_NAME, LAST_NAME, DEPARTMENT)" +
" VALUES(?, ?, ?, ?);";
public void create(EmployeeModelJDBC employeeModel) {
// NOTE: Connection code must be moved to a separate class and reused!
try (Connection connection = DriverManager.getConnection(DB_URL, "sa", "");
PreparedStatement preparedStatement =
connection.prepareStatement(INSERT_EMPLOYEE_SQL)) {
preparedStatement.setLong(1, employeeModel.getId());
preparedStatement.setString(2, employeeModel.getFirstName());
preparedStatement.setString(3, employeeModel.getLastName());
preparedStatement.setString(4, employeeModel.getDepartment());
preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
Dla JDBC trzeba stworzyć klasę DAO (Data Access Object) – oddzielną dla każdej tabeli w bazie danych. Klasa DAO będzie zawierała operacje CRUD na tabeli, dla SQL będą, to INSERT, SELECT, UPDATE oraz DELETE.
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">org.h2.Driver</property>
<property name="connection.url">jdbc:h2:~/hibernate-database</property>
<property name="connection.username">sa</property>
<property name="connection.password"></property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">1</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.H2Dialect</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create</property>
<!-- Names the annotated entity class -->
<!-- NOTE: package name shortened for readability -->
<mapping class="pl.juniorjavadeveloper.java.orm_jdbc.UserEntity"/>
</session-factory>
</hibernate-configuration>
Dla Hibernate ORM trzeba jednorazowo stworzyć plik konfiguracyjny dla połączenia z bazą danych oraz utrwalanych encji.
W Hibernate operujemy pojęciem @Entity, które pozwalają za pomocą adnotacji odwzorować elementy klasy Java (również POJO) na strukturę tabeli w bazie danych. Dla powyższej encji Hibernate automatycznie stworzy tabelę o nazwie USERS.
public class UserEntityDao {
private static SessionFactory sessionFactory;
// NOTE: this code must be moved to a separate class and then should be reused!
static {
StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
.configure("hibernate.cfg.xml")
.build();
try {
sessionFactory = new MetadataSources(serviceRegistry)
.buildMetadata()
.buildSessionFactory();
} catch (Exception e) {
e.printStackTrace();
StandardServiceRegistryBuilder.destroy(serviceRegistry);
}
}
public void create(UserEntity userEntity) {
Session session = sessionFactory.openSession();
session.beginTransaction();
session.save(userEntity);
session.getTransaction().commit();
session.close();
}
}
DAO dla Hibernate będzie używać pojęcia sesji oraz transakcji, które będą „oplatały” operacje CRUD na obiektach encji. Dla różnych encji, np.: Student, Notebook, Employee będą używane te same metody do operacji na encjach, np.: SQL INSERT, to metoda save().
Java Properties zamiast Spring @Value. Korzystając z dobrodziejstwa Spring Framework można łatwo pominąć fakt, że pewne elementy konfiguracyjne w pliku application.properties muszą być w jakiś sposób załadowane i odczytane. Do tego dochodzi fakt, że takie elementy konfiguracyjne muszą być różne dla różnych środowisk, np. dev, test, prod. Poniżej prezentuję czysto Javowe rozwiązanie w kwestii odczytu klucz/wartość z plików properties oraz jego wykorzystanie w DAO z użyciem JDBC.
import java.io.IOException;
import java.util.Properties;
public class JdbcPropertiesHelper {
private static final Properties properties = new Properties();
static {
try {
properties.load(ClassLoader.getSystemClassLoader()
.getResourceAsStream("jdbc.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}
public static String getProperty(String key) {
return properties.getProperty(key);
}
public static String getProperty(String key, String defaultValue) {
return properties.getProperty(key, defaultValue);
}
}
Powyżej przykładowa implementacja własnego mechanizmu odczytu plików properties.
Odczyt parametrów konfiguracji połączenia z bazą danych za pomocą plików properties pozwala nam odseparować się od konfiguracji oraz dostarczyć klucz/wartość dla różnych środowisk, np.: dev, test.
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
@Component
@Scope(value = "prototype")
public class Notebook {
protected int pages;
protected int currentPage;
protected Map<Integer, String> contents = new HashMap<>();
// NOTE: skipped methods for code clarity.
}
Powyżej zwykła klasa Java „zamieniona” na @Component, Bean Springowy. Kod w Spring Framework, który nałożyłem na przykład z kodem w czystej Javie.
@Component
@Scope(value = "prototype")
public class Student {
private final Notebook notebook;
public Student(Notebook notebook) {
this.notebook = notebook;
}
// NOTE: skipped methods for code clarity.
}
Powyżej zwykła klasa Java „zamieniona” na @Component, Bean Springowy.
Spring Framework bez Spring Boot. Dla powyższego kodu idealnie można przedstawić aplikację napisaną z użyciem Spring Framework, ale bez wykorzystania Spring Boot. Tak, można tworzyć aplikacje w „czystym” Spring Framework bez użycia Spring Boot, kiedyś była, to jedyna dostępna opcja. Obecnie, to podejście można wykorzystać do nieszablonowego połączenia aplikacji napisanych w języku Java, np.: Spring Framework + JavaFX – funkcjonalność DI i IoC w połączeniu z aplikacją typu desktop.
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringApplicationWithoutSpringBoot {
public static void main(String[] args) {
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(
"pl.juniorjavadeveloper.java.juniortransitionmid" +
".deepunderstanding.spring");
Student firstStudentBean = applicationContext.getBean(Student.class);
firstStudentBean.notebookPages(100);
Student secondStudentBean = applicationContext.getBean(Student.class);
secondStudentBean.notebookPages(60);
secondStudentBean.startNote("Hello World!", 11);
}
}
Powyżej kod Java tworzący kontekst aplikacji Spring ApplicationContext dla opcji z adnotacjami AnnotationConfigApplicationContext. Po stworzeniu kontekstu aplikacji, dostęp do Beanów Springowych uzyskujemy za pomocą metody getBean().
Testy jednostkowe, mockowanie, idealnie TDD. Zapomnij o „testowaniu kodu” za pomocą metody main(). Pisanie testów jednostkowych, to podstawa, jeżeli „chcemy spać spokojnie”. Jeżeli ktoś pisze testy jednostkowe, to znaczy, że rozumie tworzony system informatyczny. Koleżanka, kolega z zespołu, dwa razy się zastanowi zanim zmodyfikuje kod źródłowy, a modyfikacja spowoduje błędy w testach – chyba, że znają i stosują „model Duński” w testach 😉
Jeżeli chcemy przetestować czy działa nowo dodana encja Hibernate, to szaleństwem jest budowanie, uruchamianie aplikacji, następnie logowanie się do systemu, aby wpisać dane w formularzu na stronie web, a koniec śledzenie błędów w logach. W tej sytuacji potrzebne są testy jednostkowe bądź integracyjne. Więcej o testach jednostkowych i nie tylko można poczytać na https://martinfowler.com/testing/.
W sytuacji, kiedy nie mamy dostępu do usług zewnętrznych, np.: api firmy trzeciej, a nasz kod źródłowy, tego wymaga, to piszemy testy z wykorzystaniem mocków. Nie jest, to łatwe, ale jeżeli raz poznamy ideę mockowania, to będzie nam dużo łatwiej. Najczęściej po prostu nie chemy modyfikować pewnych zasobów, a przetestować nasz nowy kod. Przykładem jest mock dla repository w service.
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
class EmployeeServiceMockTest {
private static final long EMPLOYEE_MODEL_ID_1 = 1L;
@Test
void register() {
// given
EmployeeModelDao employeeModelDaoMock = Mockito.mock(EmployeeModelDao.class);
EmployeeService employeeService = new EmployeeService(employeeModelDaoMock);
EmployeeModel employeeModel = new EmployeeModel(null, "Tim", "Cook", "Sales");
EmployeeModel registeredEmployeeModelMock =
new EmployeeModel(EMPLOYEE_MODEL_ID_1, "Tim", "Cook", "Sales");
// when
Mockito.when(employeeService.register(employeeModel))
.thenReturn(registeredEmployeeModelMock);
EmployeeModel registeredEmployeeModel =
employeeService.register(employeeModel);
// then
Assertions.assertAll(
() -> Assertions.assertNotNull(registeredEmployeeModel,
"registeredEmployeeModel is NULL"),
() -> Assertions.assertNotNull(registeredEmployeeModel.getId(),
"registeredEmployeeModel ID is NULL"),
() -> Assertions.assertEquals(EMPLOYEE_MODEL_ID_1,
registeredEmployeeModel.getId(),
"registeredEmployeeModel ID is not equals")
);
}
}
Powyższy kod źródłowy prezentuje test jednostkowy z użyciem mocków
Rozbicie, wyizolowanie istoty problemu do postaci nowego oddzielnego projektu. Temat bardzo obszerny, a sposobów na jego rozwiązanie wiele. Dlatego nie będę przedstawiał kodu źródłowego, ale chcę umieścić ten punkt w moim artykule z prostego powodu. Na co dzień w pracy programiści, programistki zajmują się dużymi i złożonymi systemami, które mają wiele zależności. Skupiając się na jednym konkretnym wycinku tracimy z oczy szerszy kontekst.
Dla przykładu, w projekcie do istniejących @Entity mamy dodać nową encję, która będzie miała zależności do już istniejących encji. Dodajemy jedną tylko klasę encji i relację, a projekt przestaje nam działać. Kontekst Springa nie inicjalizuje się, Hibernate mówi, że nie „widzi” encji, którą dodaliśmy.
Co ja bym zrobił w takim przypadku? Wygenerował nowy projekt na start.spring.io, użył IntelliJ w wersji Community, dodał minimalny zestaw zależności, w tym przypadku spring-boot-starter-data-jpa. Skopiował bym utworząną przeze mnie encję oraz niezbędne encje zależne. Stworzyłbym testy jednostkowe „dla encji”. Następnie sukcesywnie dodawał bym kolejne elementy zależne, np.: repository, service.
Może, to się wydawać stratą czasu, ale w dłuższej perspektywie, tak nie jest. Mając na uwadze powyższe, zakładam, że w trakcie tworzenia nowego projektu zgłębiam wiedzę na temat Hibernate, JUnit, Spring Context. Rzadko kiedy mamy szansę stworzyć projekt od nowa, dodawać w nim początkowe elementy. W większości przypadków „lądujemy” w istniejącym projekcie.
Dlaczego IntelliJ w wersji Community? Zacznę od mojego ulubionego zdania w tym temacie, IntelliJ Ultimate rozleniwia i jest nadgorliwy – za dużo rzeczy robi za nas i za dużo podpowiada. Na etapie Junior i przejścia na Mid-Level Java Developer zalecam moim uczniom stosowanie IntelliJ Community. Jeżeli mamy już sporo doświadczenia i wiedzę na temat frameworków i stosowanych technologii, to IntelliJ Ultimate bardzo pomaga. Chociaż samemu cały czas używam IntelliJ w wersji Community.
Zadania na https://www.codewars.com/ – odskocznia od „nadmiaru kodu” – warstwy, loggery, frameworki – czysty, jednolinijkowy kod źródłowy. To tutaj możemy zweryfikować nasze umiejętności programistyczne, sprawdzić, co mamy jeszcze do poprawy, a co do nauki od zera. Bez goniących nas terminów w projekcie i masy codziennych rozmów z programistami i osobami zlecającymi napisanie kodu źródłowego.
Wyzwania na https://challengerocket.com/ – ChallengeRocket offers a new formula to hire outstanding candidates with IT, analytical, tech and financial skills. W tym miejscu możemy znaleźć zatrudnienie realizując wyzwania programistyczne. Weryfikując i ćwicząc nasze umiejętności programistyczne na prawdziwych zadaniach, a nie na CRUD z formularzem HTML.
„Get Trained For The Future, Today” – https://careerkarma.com/ – Career Karma gives you the information, tools and support to figure out the skills you need today that will get you the job you want in the future.
Podsumowując, nie można poruszać się tylko po powierzchni zagadnień związanych z wykorzystywanymi frameowrkami i technologiami. Dobry programista, programistka dobrze znają narzędzia, których używają. Moim zdaniem przejście z Junior na Mid-Level Java Developer nie powinno być „nominowane z urzędu, ze względu na ilość przepracowanych lat”. Powinno następować w „naturalny sposób” wynikający z ilości oraz różnorodności projektów, w których uczestniczyła dana osoba.
---- Mentor, Trainer - from zero to Junior Java Developer ----
My experience allows me to help others in retraining, changing their profession to Junior Java Developer. For 12 years I worked as an IT consultant, mainly in Java language. For 5 years I have been dealing exclusively with retraining people for Junior Java Developer.
Ta witryna wykorzystuje pliki cookies w celu świadczenia usług, dostosowania serwisu do preferencji użytkowników oraz w celach statystycznych i reklamowych. Możesz zawsze wyłączyć ten mechanizm w ustawieniach przeglądarki. Korzystanie z naszego serwisu bez zmiany ustawień przeglądarki oznacza, że cookies będą zapisane w pamięci urządzenia. AkceptujOdrzućWięcej informacji
Privacy & Cookies Policy
Privacy Overview
This website uses cookies to improve your experience while you navigate through the website. Out of these cookies, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may have an effect on your browsing experience.
Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.