├── .gitignore ├── README.md ├── docs └── programme │ ├── articles │ ├── class-files.md │ ├── io-sockets.md │ └── surrogate-keys.md │ ├── java-basics.md │ ├── jdbc.md │ ├── prereqs.md │ ├── repository-layer.md │ ├── testing.md │ └── tools.md ├── pom.xml └── src ├── main ├── java │ └── io │ │ └── qala │ │ └── javabeginner │ │ ├── ConsoleApi.java │ │ ├── ExampleWorkflow.java │ │ ├── domain │ │ ├── Card.java │ │ ├── Column.java │ │ └── User.java │ │ └── repository │ │ ├── CardRepository.java │ │ ├── ColumnRepository.java │ │ ├── UserRepository.java │ │ ├── jdbc │ │ └── JdbcColumnRepository.java │ │ └── memory │ │ ├── CardInMemoryRepository.java │ │ ├── ColumnInMemoryRepository.java │ │ └── UserInMemoryRepository.java └── resources │ └── migrations │ └── V001__InitialTables.sql └── test └── java └── io └── qala └── javabeginner └── repository └── memory └── CardInMemoryRepositoryTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | *.class 4 | target/ 5 | *.log -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Java Beginners course 2 | --- 3 | 4 | Welcome to a self paced course for those who want to become Java Engineers (if you're a big boy already, 5 | check out [Java Professionals course](https://github.com/qala-io/java-course)). This course is for you if: 6 | 7 | * You don't know programming at all or started to learn it recently 8 | * You're not sure about what to learn first and what can be postponed 9 | * You know Java syntax, but you don't know how real programmers put it all together to create real software 10 | 11 | This course doesn't replace books, in fact it relies on you reading books. It consists of a series of 12 | steps that you'll need to go through to write a realistic web app. The steps will tell you which information you 13 | need to know to accomplish the tasks, but you'll have to find and read the actual literature by yourself. 14 | 15 | # Task description 16 | 17 | You'll need to implement an analogue of [Trello](https://trello.com), so get yourself acquainted with this site. 18 | It requires a lot of efforts even to implement a very simplistic version. But our main goal is to learn 19 | real-life programming and not to earn money. 20 | 21 | If you have your own idea that you'd like to implement instead - that's great. Just make sure that: 22 | 23 | * Your app is suitable to be a web app (to work in a browser) 24 | * It will have data that needs to be stored in database 25 | * Users will have to log in to work with the app 26 | * Look over the tasks below and try to come up with some analogous tasks for your own app 27 | 28 | # Programme 29 | 30 | Within the course you're going to have steps to accomplish - most of them will be reading, practicing. There will be 31 | questions that you should be able to answer (at least to yourself) - please make sure you don't skip this part. 32 | 33 | * [Prerequisites](./docs/programme/prereqs.md) 34 | * [Domain Model](./docs/programme/java-basics.md) 35 | * [Tools](./docs/programme/tools.md) 36 | * [Repository (DAO) layer](./docs/programme/repository-layer.md) 37 | * [Testing](./docs/programme/testing.md) 38 | * JDBC Repository 39 | * Service layer 40 | * Servlets & Tomcat 41 | * HTML & JS 42 | * Authentication -------------------------------------------------------------------------------- /docs/programme/articles/class-files.md: -------------------------------------------------------------------------------- 1 | What are class files? 2 | --- 3 | 4 | Class-files are files that contain whatever we wrote in Java. But they contain this is in a much shorter form. 5 | Compilation transforms Java symbols into respective byte sequences. E.g. `=`, `+`, method invocation - this all can 6 | be represented as some byte sequence. 7 | 8 | On the one hand it saves space, but it has more to it. Another reason to convert source code into binary class-format 9 | and not keep it in java-files is that it's possible to write other programming languages like Scala, Kotlin, 10 | Clojure which have different syntax but they still are compiled into class files. 11 | 12 | Later JVM will read those byte sequences and execute whatever we asked it to. Note, that JVM is a _C++ program_ - so 13 | it just reads the class-files and depending on what we wrote there it has C++ equivalents of `if`, `for`, and other 14 | commands that it runs. You could write your own program (e.g. in Java) that reads files in your own format and if 15 | it sees a `=` sign, it notes that it needs to create a variable and store its name somewhere, and then 16 | associate that variable with the value to the right hand of `=` (you can store the name and the value in HashMap). 17 | 18 | So when they say that C++ is a Compiled Language - they say that its source code is transformed directly into CPU 19 | instructions (those also have some byte sequences). When they say that e.g. Python is an Interpreted Language - 20 | they mean that there is another program (again - could be written in C++) that reads whatever is present in Python 21 | files and transforms it into CPU commands. Java is both interpreted (JVM is written in C++) and compiled (JVM could 22 | transform some Java into C/C++ code and compile it into machine code when the _Java app is running_). -------------------------------------------------------------------------------- /docs/programme/articles/io-sockets.md: -------------------------------------------------------------------------------- 1 | How Input/Output happens under the hood 2 | --- 3 | 4 | When a program wants to write something let's say into a file, it first has to tell OS\* which file we want to work with. 5 | The file gets "opened" and OS gives back a number called "file descriptor", let's say 13243. When we read or write 6 | to that file we have to tell OS: "Here is a file descriptor 13243, and here is the data that we write: Hello". And 7 | then OS writes the data. It doesn't need a file name anymore - it just needs this file descriptor (FD). 8 | 9 | Note that writing to a console and reading user input from a console is pretty much the same as working with files. 10 | But instead of explicitly telling OS "open a console", it gets it opened when we start a process. So each program 11 | that you start has some FDs associated with it. Namely: 0 (Standard input), 1 (Standard output), 2 (Standard error). 12 | So each program can expect user input at FD=0 and print something in console at FD=1 and FD=2. Moreover if we 13 | were to close these file descriptors - our program would automatically finish. 14 | 15 | Java has special objects created to work with standard I/O, they are accessible via `System.in`, `System.out` 16 | and `System.err`. To simplify reading from `System.in` you may want to wrap `System.in` with `java.util.Scanner`. 17 | 18 | \* How does Java talk to OS? Let me remind you that JVM is just a program written in C++. This program reads 19 | instructions from class files and executes them. So if it's a C++, then it can also use C-libraries. Each OS has 20 | a list of those as part of its source code. And they do whatever is written there - e.g. they write bytes into disk 21 | and read them from disk. Thus JVM reads whatever we wrote in Java, translates this into C++ code which eventually 22 | invokes OS libs. -------------------------------------------------------------------------------- /docs/programme/articles/surrogate-keys.md: -------------------------------------------------------------------------------- 1 | Surrogate Keys 2 | --- 3 | 4 | When storing a record in DB we want to be able to fetch it back. But what attribute do we search by? Let's say 5 | we're talking about Users: 6 | 7 | * Can we search by Last Name? No, it's not unique - there could be multiple users with the same Last Name. 8 | * What about email? It is actually unique, so it could be a good identifier. Except in most apps user can change his 9 | email. Then all the parts of the app (database tables) that referenced the user by his email would have to be updated. 10 | 11 | These are 2 primary reasons why most of the time we don't use one of the business fields as an identifier. Almost 12 | always business fields are either not unique, or can be changed. That's why we add a new field specifically to 13 | identify records - usually we call it exactly that: id. 14 | 15 | In DB terminology if we use one of the business fields as a key (unique identifier) - that's called a Natural Key. 16 | If we create a special field (column) specifically for the purpose of identification - that's called a Surrogate Key. -------------------------------------------------------------------------------- /docs/programme/java-basics.md: -------------------------------------------------------------------------------- 1 | Getting acquainted with Java 2 | --- 3 | 4 | # Step1: Creating domain model 5 | 6 | Domain model describes the problem that you're solving, it contains your app's terminology, concepts and relations. 7 | It's the core of your project. 8 | 9 | * Create classes and fields: 10 | * User (email, first name, last name) 11 | * Column (name) 12 | * Card (title, description, assignee (User), creator (User), column (Column), creationTime (ZonedDateTime)) 13 | * Put them in some package (e.g. `io.[your name].[name of the app].domain`) 14 | 15 | Now, your User class should look something like this: 16 | 17 | ```java 18 | package io.qala.javabeginner.domain; 19 | 20 | public class User { 21 | private String email, firstName, lastName; 22 | 23 | public User(String email) { 24 | this.email = email; 25 | } 26 | 27 | public void setFirstName(String firstName) { 28 | this.firstName = firstName; 29 | } 30 | 31 | public void setLastName(String lastName) { 32 | this.lastName = lastName; 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return email; 38 | } 39 | } 40 | ``` 41 | 42 | Couple of things to note: 43 | 44 | * Constructor accepts only email - this is because it's a required field (I decided this just now :)). Other 45 | fields are optional. 46 | * All the fields are private. This is how it's done in 99% of cases. We don't want to expose inside information 47 | to the outside world. This is because our class may change internally while keeping its public methods. 48 | E.g. we could create a class UserName and put both firstName and lastName into that class: 49 | 50 | ```java 51 | public class User { 52 | private UserName name = new UserName(); 53 | 54 | public void setLastName(String firstName) { 55 | this.name.setFirstName(firstName); 56 | } 57 | public void setLastName(String lastName) { 58 | this.name.setLastName(lastName); 59 | } 60 | } 61 | ``` 62 | 63 | See how setLastName() and setFirstName() didn't change their signature? This means that we changed internal 64 | state of our class without breaking any other classes. So in the future when creating fields keep this trick 65 | in mind and don't expose class's internals. 66 | 67 | # Step2: Compiling & Running 68 | 69 | * Create a `main()` method to run such code: 70 | 71 | ```java 72 | Column todo = new Column("TODO"), 73 | inProgress = new Column("In Progress"), 74 | done = new Column("Done"); 75 | 76 | User creator = new User("project_manager@email.com"), 77 | assignee = new User("developer@email.com"); 78 | 79 | Card c1 = new Card("Task #1", creator, todo); 80 | c1.assignTo(assignee); 81 | c1.moveTo(inProgress); 82 | 83 | Card c2 = new Card("Task #2", creator, todo); 84 | c2.assignTo(assignee); 85 | c2.setDescription("Won't fix it, created by mistake"); 86 | c2.moveTo(done); 87 | c2.assignTo(creator); 88 | ``` 89 | 90 | * Use `System.out.println` inside your classes to produce such output: 91 | 92 | ``` 93 | User [project_manager@email.com] created a card [Task #1] in column [TODO] 94 | Card [Task #1] was assigned to [developer@email.com] 95 | Card [Task #1] was moved to [In Progress] 96 | User [project_manager@email.com] created a card [Task #2] in column [TODO] 97 | Card [Task #1] was assigned to [developer@email.com] 98 | Card [Task #1] was given a description [Won't fix it, created by mistake] 99 | Card [Task #1] was moved to [Done] 100 | Card [Task #1] was assigned to [project_manager@email.com] 101 | ``` 102 | * To make it easier to log objects' information you may want to override `toString()` method in some of your classes 103 | * Compile this code in command line and run it ([what are class files anyway?](./articles/class-files.md)) 104 | 105 | # Step3: Make it interactive 106 | 107 | * Create a console API so that user could interact with our app. Something like this: 108 | 109 | ``` 110 | First, log in! 111 | Email: winston.smith@oceania.io 112 | First name: Winston 113 | Last name: Smith 114 | Hello Winston Smith, now you can create a task. 115 | 116 | Title: Commit a thoughtcrime 117 | Column: TODO 118 | User [winston.smith@oceania.io] created a card [Commit a thoughtcrime] in column [TODO] 119 | You just created a card. Assign it to your friend. 120 | 121 | Email: julia@oceania.io 122 | First name: Julia 123 | Last name: Noname 124 | Card [Commit a thoughtcrime] was assigned to [julia@oceania.io] 125 | ``` 126 | 127 | * Note, that users' data as well as card information was entered in console 128 | * Read about how console input and output is done in Java programs under the hood ([link](./articles/io-sockets.md)) 129 | 130 | It's very rare for Java devs to create interactive console apps, but we can't go straight to web apps because it's 131 | going to be too much. We'll first build lower layers and then eventually implement a web interface. 132 | 133 | # Step4: Packaging your app 134 | 135 | JAR files are convenient for distributing Java software or libraries. Libraries - are pieces of code that someone 136 | else wrote and published, and now you can download it and use it as if it were a part of your project. 137 | 138 | * Read about jar files, create an executable jar file - it should run the code above via `java -jar myapp.jar` 139 | * Try to unpack JAR file with `jar` and Unzip utilities -------------------------------------------------------------------------------- /docs/programme/jdbc.md: -------------------------------------------------------------------------------- 1 | JDBC Repository 2 | --- 3 | 4 | Before starting this chapter you first need to read about JDBC in your book. 5 | 6 | Ideally we wouldn't care about databases until Web part is usable - database is hard to change so we could first 7 | find out what exactly we need by building Web UI. But in your introductory books you will have a chapter about 8 | JDBC while Web requires additional books. That's why we'll first tackle DB side. 9 | 10 | # Step1: Create Repository interfaces 11 | 12 | * Rename your current repositories to XxxInMemoryRepository e.g. CardInMemoryRepository 13 | * Add interfaces XxxRepository e.g. CardRepository and pull all public methods from your InMemoryRepositories 14 | to these new interfaces 15 | * Each XxxInMemoryRepository needs to implement interface XxxRepository 16 | * Everywhere where XxxInMemoryRepository is used as a type of a variable - change that to interfaces. E.g. 17 | 18 | ```java 19 | private final CardInMemoryRepository cardRepository = new CardInMemoryRepository(); 20 | ``` 21 | needs to change to 22 | ```java 23 | private final CardRepository cardRepository = new CardInMemoryRepository(); 24 | ``` 25 | 26 | Note, that IntelliJ allows doing all of this automatically: Refactor->Extract->Interface. 27 | 28 | * You should also rename your test classes according to the new names of production classes. 29 | 30 | Couple of things to note: 31 | 32 | * This all is needed so that we could implement interfaces with new classes and easily replace the implementation 33 | in all the places. With small efforts we could even make it possible to switch between InMemory and JDBC 34 | implementations. 35 | * We could've created the interfaces right away when we were creating our first repository implementation. But 36 | that would be premature because at that point we didn't know if we needed a different implementation (well I did 37 | but I pretended as if I didn't). If you found a way to do this whole refactoring with IDE capabilities you can 38 | see how simple it is to introduce interfaces at some point in the future without over-engineering at the beginning. 39 | 40 | # Step2: Create DB scheme 41 | 42 | For our purposes we'll be using H2 database. H2 is an in-memory database written in Java. We don't frequently use 43 | such databases for real apps. Instead we use standalone databases like MySQL, PostgreSQL, Oracle. But H2 is very 44 | useful because we can easily write automated tests with it (instead of deploying a standalone DB locally), and also 45 | it's very easy to add to the project. 46 | 47 | * Add H2 JDBC driver to Maven dependencies 48 | * Now something should create tables when the app is starting (because we use in-memory DB each time we restart 49 | the app everything is going to be cleared). For that: 50 | * Add Flyway tool as a dependency 51 | * Create Flyway SQL Migration that creates tables for each of your domain classes. Such files go to 52 | `src/main/resources` folder. 53 | * Start `new Flyway().migrate()` migrations from one of your main classes 54 | 55 | # Step3: Create JDBC Repositories 56 | 57 | 58 | * Implement XxxJdbcRepository for each interface 59 | * Create tables for each of our domain object. Use Foreign Keys (FK) to reference records from other tables. 60 | Usually we use tools like Flyway in order to create these tables, but for now you could run these statements 61 | in one of the classes in your app. 62 | * For now create Connection object every time you need to run SQL query 63 | 64 | -------------------------------------------------------------------------------- /docs/programme/prereqs.md: -------------------------------------------------------------------------------- 1 | * Start reading a book for Java beginners (e.g. 2 volumes by [Horstmann](http://horstmann.com/corejava/index.html)) 2 | * Things that you need to know before starting next steps: 3 | * Primitives, strings 4 | * Loops and conditions: `if`, `for` 5 | * Classes, fields, static fields, methods, constructors, objects (instances) 6 | * Difference between comparing references (`==`) and data (`equals()`) 7 | * You can read the rest of the book in parallel with this course 8 | * Things that you can skip completely: 9 | * AWT, Swing or JavaFX (tools for desktop apps). We're going to build a web app, desktop development is 10 | not very popular nowadays. 11 | * Stream API (don't confuse with IO streams) and closures aren't important unless you want to understand 12 | someone else's code that utilizes them. 13 | * Get acquainted with official Java naming/formatting conventions, there will be examples in this course so you 14 | should be able to get the drift without giving it much of a thought. But understand that it's common in dev 15 | community to get angry if they see common conventions violated. So start getting used to the conventions from the 16 | beginning. -------------------------------------------------------------------------------- /docs/programme/repository-layer.md: -------------------------------------------------------------------------------- 1 | Repository Layer 2 | --- 3 | 4 | So at the moment we have Domain Model and a console interface. It's time to store data in a database. In Java there 5 | are special interfaces and classes to work with DB - they are part of so called JDBC. And we could use 6 | them in all parts of the project to run SQL queries. But this is usually a bad idea. We need to isolate 7 | DB-related logic into separate layer called Repository (or DAO - data access object). Why? 8 | 9 | * It's just easier to structure the project - you always know where to find SQL 10 | * This eliminates code duplication as oftentimes we need the same query to be executed for different parts of app 11 | * There are different types of code - could be domain logic, or database logic, or invocation of other services - 12 | it's hard to understand code if it's all mixed together 13 | 14 | # Add Repository layer 15 | 16 | At the moment we're not ready to work with real databases, so for now let's store objects in memory. Then we'll 17 | replace these classes with another implementation so that the rest of the app won't have to be changed a lot. So 18 | for now: 19 | 20 | * Learn how these data structures work and when they can be useful: 21 | * ArrayList 22 | * Hash Map (aka Hash Table) with external chaining is used in `java.util.HashMap` 23 | * Look into other Hash-based maps: LinkedHashMap, IdentityHashMap. Note, that the latter implements a 24 | completely different structure - a Hash Map with linear probing. 25 | * To each class of our Domain Model add a field: `String id`. This is going to be our 26 | [surrogate key](./articles/surrogate-keys.md). 27 | * Create classes: `UserRepository`, `CardRepository`, `ColumnRepository` 28 | * Add methods to retrieve and save objects from/to "database". These methods should store data in Java Collections: 29 | HashMap would be the most useful in this case because the identifier of the object could be stored as a key in the 30 | map. 31 | * When saving an object to our "database" Repository classes need to assign ID. The easiest option is to 32 | assign a random one: `UUID.randomUUID().toString()` 33 | * If you want to retrieve objects by some other unique field (e.g. Columns could be retrieved by name) you can 34 | create additional maps. This is an equivalent of creating indices in DB. 35 | * Example of a repository class: 36 | 37 | ```java 38 | package io.qala.javabeginner.repository; 39 | 40 | import io.qala.javabeginner.domain.Card; 41 | import io.qala.javabeginner.domain.Column; 42 | 43 | import java.util.*; 44 | 45 | public class CardRepository { 46 | private final Map cardById = new LinkedHashMap<>(); 47 | private final Map> cardByAssignee = new HashMap<>(); 48 | private final Map> cardByColumn = new HashMap<>(); 49 | 50 | public void save(Card newCard) { 51 | if(newCard.getId() != null) 52 | throw new IllegalArgumentException("The card already stored in DB: " + newCard.getId()); 53 | newCard.setId(UUID.randomUUID().toString()); 54 | cardById.put(newCard.getId(), newCard); 55 | List cardsByColumn = cardByColumn.computeIfAbsent(newCard.getColumn().getId(), (c) -> new ArrayList<>()); 56 | cardsByColumn.add(newCard); 57 | List assigneeCards = cardByAssignee.computeIfAbsent(newCard.getAssignee().getId(), (c) -> new ArrayList<>()); 58 | assigneeCards.add(newCard); 59 | } 60 | public List findByColumn(Column column) { 61 | return cardByColumn.get(column.getId()); 62 | } 63 | 64 | } 65 | 66 | ``` 67 | 68 | # Improve console interface 69 | 70 | Now let's create a feeling of a real app in the console. 71 | 72 | * Make it possible to choose a command - list cards, create a card. User can keep creating cards and listing 73 | them. 74 | * Every time we move to a column - if such column exists in repository, we re-use that column 75 | * Every time we assign a task to a user - if such user exists, we re-use that user 76 | 77 | Example of communication: 78 | 79 | ``` 80 | First, log in! 81 | Email: manager@email.com 82 | First name: Shawn 83 | Last name: Spencer 84 | Hello Shawn Spencer, now you can create a task. 85 | Choose 1 to create a card, 2 to show the list of cards, 0 to exit 86 | 1 87 | Title: Task #1 88 | Column: TODO 89 | 15:01:25.076 [main] INFO io.qala.javabeginner.domain.Card - User [manager@email.com] created a card [Task #1] in column [TODO] 90 | You just created a card. Assign it to your friend. 91 | Email: developer@email.com 92 | First name: Gus 93 | Last name: Burton 94 | 15:01:33.986 [main] INFO io.qala.javabeginner.domain.Card - Card [Task #1] was assigned to [developer@email.com] 95 | Choose 1 to create a card, 2 to show the list of cards, 0 to exit 96 | 1 97 | Title: Task #2 98 | Column: TODO 99 | 15:01:43.592 [main] INFO io.qala.javabeginner.domain.Card - User [manager@email.com] created a card [Task #2] in column [TODO] 100 | You just created a card. Assign it to your friend. 101 | Email: manager@email.com 102 | 15:01:49.721 [main] INFO io.qala.javabeginner.domain.Card - Card [Task #2] was assigned to [manager@email.com] 103 | Choose 1 to create a card, 2 to show the list of cards, 0 to exit 104 | 1 105 | Title: Task #3 106 | Column: Done 107 | 15:02:22.344 [main] INFO io.qala.javabeginner.domain.Card - User [manager@email.com] created a card [Task #3] in column [Done] 108 | You just created a card. Assign it to your friend. 109 | Email: developer@email.com 110 | 15:02:27.857 [main] INFO io.qala.javabeginner.domain.Card - Card [Task #4] was assigned to [developer@email.com] 111 | Choose 1 to create a card, 2 to show the list of cards, 0 to exit 112 | 2 113 | TODO: Task #1(Gus Burton), Task #2(Shawn Spencer) 114 | Done: Task #3(Gus Burton) 115 | Choose 1 to create a card, 2 to show the list of cards, 0 to exit 116 | 0 117 | ``` 118 | 119 | And here is a glimpse of how things can be organized: 120 | 121 | ```java 122 | private void run() { 123 | System.out.println("First, log in!"); 124 | User currentUser = enterUser(in); 125 | System.out.println("Hello " + currentUser.getFullName() + ", now you can create a task."); 126 | for(;;) { 127 | System.out.println("Choose 1 to create a card, 2 to show the list of cards, 0 to exit"); 128 | String command = in.nextLine(); 129 | if(command.equals("0")) 130 | return; 131 | else if(command.equals("1")) 132 | createCard(currentUser); 133 | else if(command.equals("2")) 134 | showCards(); 135 | else 136 | System.out.println("The command was not recognized, try again"); 137 | } 138 | } 139 | private void showCards() { 140 | for (Column column : columnRepository.findAllOrderedByPosition()) { 141 | String line = column.getName() + ":\t"; 142 | for (Card card : cardRepository.findByColumn(column)) 143 | line += card.getTitle() + "(" + card.getAssignee().getFullName() + "), "; 144 | line = line.substring(0, line.length() - 2); 145 | System.out.println(line); 146 | } 147 | } 148 | ``` 149 | 150 | After you implement this you may already start appreciating Repository Layer. Without it you'd scatter all those 151 | HashMaps all over the code. -------------------------------------------------------------------------------- /docs/programme/testing.md: -------------------------------------------------------------------------------- 1 | Automated Testing 2 | --- 3 | 4 | You already have a working interactive app. But: 5 | 6 | * Does it work in all cases with all possible inputs? Will it work nicely if user enters nothing for his email or 7 | first name? In order to check what would happen you should now run the app and manually check this case and others. 8 | * Let's say it works correctly _now_, will it work tomorrow after you introduce new changes? 9 | 10 | The process of checking software manually is called Manual Testing. But developers often write automated checks: 11 | source code (a test) to check another piece of source code (production code). These are called Automated Tests, 12 | and they are crucial in modern software engineering. 13 | 14 | # Step1: Preparation for testing 15 | 16 | * Add a [JUnit4 dependency](https://mvnrepository.com/artifact/junit/junit) to your Maven. This is a library that 17 | will help us write and run tests. There are 3 popular choices: JUnit4, TestNG which are 18 | simpler and JUnit5 which is more complicated. 19 | * The scope of this Maven dependency must be `test` 20 | * Create `src/test/java` folder, there you can create a package with your test classes. We usually place 21 | test classes into the same packages where the classes-under-test are located. So same package, but not in 22 | `src/main/java`. 23 | * Learn what asserts are in JUnit and also read about annotations `@Test`, `@Before`, `@After`, 24 | `@BeforeClass`, `@AfterClass` 25 | * Write some basic test which fails (e.g. `assertTrue(false)`). Try running `mvn test` and make sure that 26 | you see what you expect - that the test failed. Also try running this test from IDE. 27 | * Then make the test pass and also run with Maven and with the IDE. 28 | 29 | # Step2: Writing tests 30 | 31 | * Write tests for all your repositories. E.g. here is a list of tests that make sense for `CardRepository`: 32 | * savingCardAssignsId 33 | * savingCardLetsUsFindItByColumn 34 | * ifNoCardsInColumn_thenEmptyCardListIsReturned 35 | 36 | Example: 37 | 38 | ```java 39 | public class CardRepositoryTest { 40 | @Test 41 | public void savingCardAssignsId() { 42 | CardRepository repo = new CardRepository(); 43 | Card newCard = cardInColumn(column()); 44 | 45 | repo.save(newCard); 46 | assertNotNull(newCard.getId()); 47 | } 48 | 49 | private static Column column() { 50 | Column column = new Column("TODO"); 51 | new ColumnRepository().save(column); 52 | return column; 53 | } 54 | private static Card cardInColumn(Column column) { 55 | return new Card("title", new User("creator@email.com"), column); 56 | } 57 | } 58 | ``` 59 | 60 | Note that we created 2 private methods to aid with readability. It's important for tests to be easy to read: 61 | they should state intentions, expectations and the code should be business-oriented (as opposed to technical - 62 | with loops and conditions). Tests should also be simple - they need to check small pieces of functionality. 63 | 64 | You'll need to keep writing tests for each new piece of functionality that you add. 65 | 66 | # Summary 67 | 68 | Such testing is important because: 69 | 70 | * It lets us check the functionality faster. Often we write software with defects - fixing them and then 71 | re-running the whole app to manually check the fixes can actually be slower. 72 | * It helps checking whether the old functionality still works after new changes are introduced. When this happens - 73 | it irritates everyone and slows down the whole project, so it's better to protect yourself and your colleagues 74 | by writing automated checks. 75 | * It makes it easier to introduce newcomers to the team. It's too easy for new colleagues to introduce bugs because they 76 | don't know the project well yet. But if tests are in place there's some safety net. 77 | 78 | If you write tests for new functionality (or check it manually) - it's called Acceptance Testing (don't confuse 79 | with similarly sounding User Acceptance Testing - UAT). If you check that old functionality works - it's called 80 | Regression Testing. So usually we write Acceptance Tests which soon after that turn into Regression Tests. 81 | 82 | When your app grows your Regression suite grows as well. Often the amount of tests is greater than the amount of 83 | production code. So take this seriously. 84 | 85 | There is also a notion of TDD (Test Driven Development): you write tests first (they fail of course) 86 | and _only then_ you write production code _to let the tests pass_. It's crucial that you don't write all tests 87 | right away and then implement the whole functionality: at the beginning you implement an impaired functionality - 88 | something that works only in specific cases. And then you write more and more tests and implement more and more of 89 | production code. 90 | 91 | TDD might make you more productive especially if the feature you write is very big and complicated - 92 | TDD makes you concentrate on small pieces and not be overwhelmed. There is a book 93 | [Test Driven Development: By Example](https://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530) 94 | by Kent Beck that explains how to do TDD right. 95 | 96 | To TDD or not to TDD is your choice - the end result shouldn't depend on how you got there. -------------------------------------------------------------------------------- /docs/programme/tools.md: -------------------------------------------------------------------------------- 1 | Java Tools 2 | --- 3 | 4 | # Step1: IDE 5 | 6 | You can't keep working in command line forever. Average apps have tons of code (>50K lines and hundreds to 7 | thousands of classes), it would be a nightmare to write it all in a notepad and compile it all in command line. 8 | That's why you need to use IDE - it will greatly simplify your life. 9 | 10 | * Install an IDE like IntelliJ IDEA (Community Edition is free) or Eclipse 11 | * Open your project, set up SDK (Java installation) 12 | * Try running your console app from IDE 13 | * Try debugging your app - putting a breakpoint and then running the app in Debug mode; see variables' state in 14 | the running app; evaluate your own expressions. Debugging is going to be one of the most important tools in 15 | your career, so play with it. 16 | 17 | # Step2: Logging 18 | 19 | While `System.out.println` worked fine for your simple case, you'll have to stop using it: 20 | 21 | * First of all it's just not convenient to concatenate strings and values all the time 22 | * Most of the time we want to write logs into files and not into console (though we could do both) 23 | * We'd like to categorize different lines of log - some of them are written because there are errors, others - 24 | just to explain what's happening with the system at any moment of time. We'd like to draw attention to error logs. 25 | 26 | That's why instead - we use special libraries for logging. The most popular choice nowadays is SLF4J+Logback. 27 | The former provides interfaces, the latter provides the implementation. So: 28 | 29 | * Download 3 libs (jar files): slf4j-api, logback-classic, logback-core. 30 | You can find them on the official sites, but there is also a central repository of most of the libraries that Java 31 | devs need. You can find all different versions of different libs there: https://mvnrepository.com/ 32 | * Add these jar files in your project dependencies in IDE 33 | * After they are added, you can try using logging instead of `System.out.println`. See 34 | [Logback documentation](http://logback.qos.ch/manual/). Your Domain classes should log messages like this: 35 | 36 | ```java 37 | import org.slf4j.Logger; 38 | import org.slf4j.LoggerFactory; 39 | 40 | public class Card { 41 | private static final Logger LOG = LoggerFactory.getLogger(Card.class); 42 | 43 | //... 44 | 45 | public void assignTo(User assignee) { 46 | this.assignee = assignee; 47 | LOG.info("Card [{}] was assigned to [{}]", title, assignee.getEmail()); 48 | } 49 | } 50 | ``` 51 | When you run your app next time, you should see something like this in console: 52 | 53 | ``` 54 | 02:35:12.535 [main] INFO io.qala.javabeginner.domain.Card - User [winston.smith@oceania.io] created a card [Commit a thoughtcrime] in column [TODO] 55 | ``` 56 | 57 | * The format in which this string shows up in console can be configured - you should find some examples of 58 | `logback.xml` file on the Internet. 59 | * You may also configure Logback to put these lines into some file 60 | * Try unpacking jar files that you downloaded - find the classes that you imported in your code 61 | * Note that you can Ctrl+Click on the class in IDE and it will decompile the class from JAR for you 62 | * Answer this question: why was `LOG` field marked as `static`? Can we leave out `static`? Does it make sense 63 | to do so? 64 | 65 | # Step3: Build Tools 66 | 67 | While IDE let's you easily navigate through your project, that's not enough: 68 | 69 | * It's hard for a newcomer to join the project because he would have to set up his IDE, download all of the 70 | dependencies (there could be hundreds of them). That's tedious and error-prone. 71 | * You don't want to manually package the binary for further deployment. Team members' local configuration may 72 | differ from each other, but you want to guarantee that the packaged JAR file always works. That's why we set up so 73 | called Build Servers (Jenkins, TeamCity, etc) - these are tools that run on remote servers and can package your 74 | app. Those often don't have UI set up in their OS and they can't use IDE. 75 | * We also would like to have some life-cycle in projects - it would compile things in the right order, run checks, 76 | package a jar file, upload it somewhere. 77 | 78 | That's why we have special Build Tools. Most notorious examples are: Ant, Maven, Gradle. The most used one is Maven. 79 | So: 80 | 81 | * Read some introductory articles about Maven 82 | * Download and install Maven locally. It's an archive that needs to be unpacked and you'll need to set up M2_HOME 83 | env variable as well as put `M2_HOME/bin` into your `path` variable. 84 | * Create `pom.xml` file in your project, where you specify: 85 | * groupId/artifactId/version of your project 86 | * Dependencies (slf4j-api, logback-classic, logback-core) 87 | * Configuration of maven-jar-plugin so that it produces an executable jar 88 | * Try building your project with `mvn package` - if it's successful, you should get a jar file in your `target` 89 | directory that is executable with `java -jar` 90 | * Delete all your IDE files and try to re-open the project with IDE. Your IDE should recognize that it's a Maven 91 | project and add libraries to its Dependencies automatically. 92 | 93 | At some point you'll need to 94 | [learn Maven deeper](https://github.com/qala-io/java-course/blob/master/docs/programme/maven.md), but for now 95 | your main goal is to learn Java. Maven will simplify some of our further steps. 96 | 97 | # Step4: Version control system (VCS) 98 | 99 | VCS allows keeping track of who & when added a change in the source code. This allows: 100 | 101 | * Reverting the changes to previous version (e.g. the change introduced a critical bug) 102 | * Understand who and why added this or that line of code (we create description when we add changes to VCS) 103 | * Reviewing the changes done by other team members 104 | 105 | There are plenty of tools that allow doing this, but today by far the most widespread is Git. You need to start 106 | using it, so find some tutorials and: 107 | 108 | * Create a Git repository at GitHub or BitBucket or any other free Git Server 109 | * Create a `.gitignore` file and list things that you want to exclude. Generally we track only changes in the source 110 | code, so everything else (`target/`, `*.log`, your IDE-related files) have to be excluded 111 | * Push all your current code into the repo 112 | * From now on keep committing & pushing when you change something in the code 113 | 114 | There are 2 types of people who use Git - those who don't know it and those who know how it works internally. So 115 | if you want to be a professional, at some point you'll need to read a chapter on 116 | [Git Internals](https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain) and find out what Blob, 117 | Tree and Commit objects are, and what symbolic references are. You can also find video lessons on this topic on 118 | Youtube. -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | io.qala.java-beginner-course 8 | java-beginner-course 9 | 1-SNAPSHOT 10 | 11 | 12 | com.h2database 13 | h2 14 | 2.2.220 15 | 16 | 17 | org.flywaydb 18 | flyway-core 19 | 5.2.4 20 | 21 | 22 | 24 | org.slf4j 25 | slf4j-api 26 | 1.7.13 27 | 28 | 29 | 32 | ch.qos.logback 33 | logback-classic 34 | 1.3.12 35 | runtime 36 | 37 | 38 | junit 39 | junit 40 | 4.13.1 41 | test 42 | 43 | 44 | 45 | 46 | 47 | org.apache.maven.plugins 48 | maven-compiler-plugin 49 | 50 | 8 51 | 8 52 | 53 | 54 | 55 | 56 | jar 57 | 58 | -------------------------------------------------------------------------------- /src/main/java/io/qala/javabeginner/ConsoleApi.java: -------------------------------------------------------------------------------- 1 | package io.qala.javabeginner; 2 | 3 | import io.qala.javabeginner.domain.Card; 4 | import io.qala.javabeginner.domain.Column; 5 | import io.qala.javabeginner.domain.User; 6 | import io.qala.javabeginner.repository.CardRepository; 7 | import io.qala.javabeginner.repository.ColumnRepository; 8 | import io.qala.javabeginner.repository.UserRepository; 9 | import io.qala.javabeginner.repository.memory.CardInMemoryRepository; 10 | import io.qala.javabeginner.repository.memory.ColumnInMemoryRepository; 11 | import io.qala.javabeginner.repository.memory.UserInMemoryRepository; 12 | import org.flywaydb.core.Flyway; 13 | import org.flywaydb.core.api.configuration.FluentConfiguration; 14 | import org.h2.jdbcx.JdbcDataSource; 15 | 16 | import java.util.Scanner; 17 | 18 | public class ConsoleApi { 19 | private final UserRepository userRepository = new UserInMemoryRepository(); 20 | private final CardRepository cardRepository = new CardInMemoryRepository(); 21 | private final ColumnRepository columnRepository = new ColumnInMemoryRepository(); 22 | 23 | private final Scanner in = new Scanner(System.in); 24 | 25 | public static void main(String[] args) { 26 | new ConsoleApi().run(); 27 | } 28 | 29 | private void run() { 30 | JdbcDataSource dataSource = new JdbcDataSource(); 31 | dataSource.setUrl("jdbc:h2:mem:javabeginner;DB_CLOSE_DELAY=-1"); 32 | dataSource.setUser("sa"); 33 | new Flyway(new FluentConfiguration().dataSource(dataSource).locations("migrations")).migrate(); 34 | 35 | System.out.println("First, log in!"); 36 | User currentUser = enterUser(in); 37 | System.out.println("Hello " + currentUser.getFullName() + ", now you can create a task."); 38 | for(;;) { 39 | System.out.println("Choose 1 to create a card, 2 to show the list of cards, 0 to exit"); 40 | String command = in.nextLine(); 41 | if(command.equals("0")) 42 | return; 43 | else if(command.equals("1")) 44 | createCard(currentUser); 45 | else if(command.equals("2")) 46 | showCards(); 47 | else 48 | System.out.println("The command was not recognized, try again"); 49 | } 50 | } 51 | private void showCards() { 52 | for (Column column : columnRepository.findAllOrderedByPosition()) { 53 | String line = column.getName() + ":\t"; 54 | for (Card card : cardRepository.findByColumn(column)) 55 | line += card.getTitle() + "(" + card.getAssignee().getFullName() + "), "; 56 | line = line.substring(0, line.length() - 2); 57 | System.out.println(line); 58 | } 59 | } 60 | private Card createCard(User currentUser) { 61 | System.out.print("Title: "); 62 | String title = in.nextLine(); 63 | System.out.print("Column: "); 64 | String columnName = in.nextLine(); 65 | Column column = columnRepository.findByName(columnName); 66 | if(column == null) { 67 | column = new Column(columnName); 68 | columnRepository.save(column); 69 | } 70 | Card card = new Card(title, currentUser, column); 71 | System.out.println("You just created a card. Assign it to your friend."); 72 | 73 | User assignee = enterUser(in); 74 | card.assignTo(assignee); 75 | cardRepository.save(card); 76 | return card; 77 | } 78 | 79 | private User enterUser(Scanner in) { 80 | System.out.print("Email: "); 81 | String email = in.nextLine(); 82 | User currentUser = userRepository.findByEmail(email); 83 | if(currentUser == null) { 84 | currentUser = new User(email); 85 | 86 | System.out.print("First name: "); 87 | currentUser.setFirstName(in.nextLine()); 88 | System.out.print("Last name: "); 89 | currentUser.setLastName(in.nextLine()); 90 | userRepository.saveOrUpdate(currentUser); 91 | } 92 | return currentUser; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/io/qala/javabeginner/ExampleWorkflow.java: -------------------------------------------------------------------------------- 1 | package io.qala.javabeginner; 2 | 3 | import io.qala.javabeginner.domain.Card; 4 | import io.qala.javabeginner.domain.Column; 5 | import io.qala.javabeginner.domain.User; 6 | 7 | public class ExampleWorkflow { 8 | public static void main(String[] args) { 9 | Column todo = new Column("TODO"), 10 | inProgress = new Column("In Progress"), 11 | done = new Column("Done"); 12 | 13 | User creator = new User("project_manager@email.com"), 14 | assignee = new User("developer@email.com"); 15 | 16 | Card c1 = new Card("Task #1", creator, todo); 17 | c1.assignTo(assignee); 18 | c1.moveTo(inProgress); 19 | 20 | Card c2 = new Card("Task #2", creator, todo); 21 | c2.assignTo(assignee); 22 | c2.setDescription("Won't fix it, created by mistake"); 23 | c2.moveTo(done); 24 | c2.assignTo(creator); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/io/qala/javabeginner/domain/Card.java: -------------------------------------------------------------------------------- 1 | package io.qala.javabeginner.domain; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.time.ZonedDateTime; 7 | 8 | public class Card { 9 | private static final Logger LOG = LoggerFactory.getLogger(Card.class); 10 | private String id, title, description; 11 | private User assignee, creator; 12 | private Column column; 13 | private ZonedDateTime creationTime; 14 | 15 | public Card(String title, User creator, Column column) { 16 | this.title = title; 17 | this.creator = creator; 18 | this.column = column; 19 | this.creationTime = ZonedDateTime.now(); 20 | LOG.info("User [{}] created a card [{}] in column [{}]", creator.getEmail(), title, column.getName()); 21 | } 22 | 23 | public String getId() { 24 | return id; 25 | } 26 | 27 | public void setId(String id) { 28 | this.id = id; 29 | } 30 | 31 | public void assignTo(User assignee) { 32 | this.assignee = assignee; 33 | LOG.info("Card [{}] was assigned to [{}]", title, assignee.getEmail()); 34 | } 35 | 36 | public User getAssignee() { 37 | return assignee; 38 | } 39 | 40 | public void moveTo(Column column) { 41 | this.column = column; 42 | LOG.info("Card [{}] was moved to [{}]", title, column.getName()); 43 | } 44 | public Column getColumn() { 45 | return column; 46 | } 47 | 48 | public void setDescription(String description) { 49 | this.description = description; 50 | LOG.info("Card [{}] was given a description [{}]", title, description); 51 | } 52 | public String getTitle() { 53 | return title; 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return "Card [" + title + ']'; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/io/qala/javabeginner/domain/Column.java: -------------------------------------------------------------------------------- 1 | package io.qala.javabeginner.domain; 2 | 3 | public class Column { 4 | private String id, name; 5 | private int positionOnBoard; 6 | 7 | public Column(String name) { 8 | this.name = name; 9 | } 10 | 11 | public String getName() { 12 | return name; 13 | } 14 | 15 | public String getId() { 16 | return id; 17 | } 18 | 19 | public void setId(String id) { 20 | this.id = id; 21 | } 22 | public void setPositionOnBoard(int position) { 23 | this.positionOnBoard = position; 24 | } 25 | public int getPositionOnBoard() { 26 | return positionOnBoard; 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return name; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/io/qala/javabeginner/domain/User.java: -------------------------------------------------------------------------------- 1 | package io.qala.javabeginner.domain; 2 | 3 | public class User { 4 | private String id, email, firstName, lastName; 5 | 6 | public User(String email) { 7 | this.email = email; 8 | } 9 | 10 | public String getId() { 11 | return id; 12 | } 13 | 14 | public void setId(String id) { 15 | this.id = id; 16 | } 17 | 18 | public void setFirstName(String firstName) { 19 | this.firstName = firstName; 20 | } 21 | 22 | public void setLastName(String lastName) { 23 | this.lastName = lastName; 24 | } 25 | 26 | public String getEmail() { 27 | return email; 28 | } 29 | 30 | public String getFullName() { 31 | return firstName + " " + lastName; 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return email; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/io/qala/javabeginner/repository/CardRepository.java: -------------------------------------------------------------------------------- 1 | package io.qala.javabeginner.repository; 2 | 3 | import io.qala.javabeginner.domain.Card; 4 | import io.qala.javabeginner.domain.Column; 5 | 6 | import java.util.List; 7 | 8 | public interface CardRepository { 9 | void save(Card newCard); 10 | 11 | List findByColumn(Column column); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/io/qala/javabeginner/repository/ColumnRepository.java: -------------------------------------------------------------------------------- 1 | package io.qala.javabeginner.repository; 2 | 3 | import io.qala.javabeginner.domain.Column; 4 | 5 | import java.util.List; 6 | 7 | public interface ColumnRepository { 8 | void save(Column column); 9 | 10 | Column findByName(String name); 11 | 12 | List findAllOrderedByPosition(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/io/qala/javabeginner/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package io.qala.javabeginner.repository; 2 | 3 | import io.qala.javabeginner.domain.User; 4 | 5 | public interface UserRepository { 6 | void saveOrUpdate(User newUser); 7 | 8 | User findByEmail(String email); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/io/qala/javabeginner/repository/jdbc/JdbcColumnRepository.java: -------------------------------------------------------------------------------- 1 | package io.qala.javabeginner.repository.jdbc; 2 | 3 | import io.qala.javabeginner.domain.Column; 4 | import io.qala.javabeginner.repository.ColumnRepository; 5 | 6 | import java.sql.Connection; 7 | import java.util.List; 8 | 9 | public class JdbcColumnRepository implements ColumnRepository { 10 | private final Connection connection; 11 | 12 | public JdbcColumnRepository(Connection connection) { 13 | this.connection = connection; 14 | } 15 | 16 | @Override public void save(Column column) { 17 | 18 | } 19 | 20 | @Override public Column findByName(String name) { 21 | return null; 22 | } 23 | 24 | @Override public List findAllOrderedByPosition() { 25 | return null; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/io/qala/javabeginner/repository/memory/CardInMemoryRepository.java: -------------------------------------------------------------------------------- 1 | package io.qala.javabeginner.repository.memory; 2 | 3 | import io.qala.javabeginner.domain.Card; 4 | import io.qala.javabeginner.domain.Column; 5 | import io.qala.javabeginner.repository.CardRepository; 6 | 7 | import java.util.*; 8 | 9 | import static java.util.Collections.emptyList; 10 | 11 | public class CardInMemoryRepository implements CardRepository { 12 | private final Map cardById = new LinkedHashMap<>(); 13 | private final Map> cardByColumn = new HashMap<>(); 14 | 15 | @Override 16 | public void save(Card newCard) { 17 | if(newCard.getId() != null) 18 | throw new IllegalArgumentException("The card already stored in DB: " + newCard.getId()); 19 | newCard.setId(UUID.randomUUID().toString()); 20 | cardById.put(newCard.getId(), newCard); 21 | List cardsByColumn = cardByColumn.computeIfAbsent(newCard.getColumn().getId(), (c) -> new ArrayList<>()); 22 | cardsByColumn.add(newCard); 23 | } 24 | @Override 25 | public List findByColumn(Column column) { 26 | List cards = cardByColumn.get(column.getId()); 27 | if(cards == null) 28 | cards = emptyList(); 29 | return cards; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/qala/javabeginner/repository/memory/ColumnInMemoryRepository.java: -------------------------------------------------------------------------------- 1 | package io.qala.javabeginner.repository.memory; 2 | 3 | import io.qala.javabeginner.domain.Column; 4 | import io.qala.javabeginner.repository.ColumnRepository; 5 | 6 | import java.util.*; 7 | 8 | public class ColumnInMemoryRepository implements ColumnRepository { 9 | private final Map columnsById = new HashMap<>(); 10 | private final Map columnsByName = new HashMap<>(); 11 | 12 | @Override public void save(Column column) { 13 | if(column.getId() != null) 14 | throw new IllegalArgumentException("The column already stored in DB: " + column.getId()); 15 | column.setId(UUID.randomUUID().toString()); 16 | columnsById.put(column.getId(), column); 17 | columnsByName.put(column.getName(), column); 18 | } 19 | @Override public Column findByName(String name) { 20 | return columnsByName.get(name); 21 | } 22 | @Override public List findAllOrderedByPosition() { 23 | return new ArrayList<>(orderedByPosition(columnsById.values())); 24 | } 25 | 26 | private List orderedByPosition(Collection unordered) { 27 | List ordered = new ArrayList<>(unordered); 28 | ordered.sort(Comparator.comparingInt(Column::getPositionOnBoard)); 29 | return ordered; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/qala/javabeginner/repository/memory/UserInMemoryRepository.java: -------------------------------------------------------------------------------- 1 | package io.qala.javabeginner.repository.memory; 2 | 3 | import io.qala.javabeginner.domain.User; 4 | import io.qala.javabeginner.repository.UserRepository; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.UUID; 9 | 10 | public class UserInMemoryRepository implements UserRepository { 11 | private final Map usersById = new HashMap<>(); 12 | private final Map usersByEmail = new HashMap<>(); 13 | 14 | @Override 15 | public void saveOrUpdate(User newUser) { 16 | if(newUser.getId() == null) 17 | newUser.setId(UUID.randomUUID().toString()); 18 | else { 19 | String oldEmail = usersById.get(newUser.getId()).getEmail(); 20 | if(!oldEmail.equals(newUser.getEmail())) 21 | usersByEmail.remove(oldEmail); 22 | } 23 | usersById.put(newUser.getId(), newUser); 24 | usersByEmail.put(newUser.getEmail(), newUser); 25 | } 26 | @Override 27 | public User findByEmail(String email) { 28 | return usersByEmail.get(email); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/migrations/V001__InitialTables.sql: -------------------------------------------------------------------------------- 1 | create table USER ( 2 | ID varchar(36) primary key, 3 | EMAIL nvarchar(128) not null, 4 | FIRST_NAME nvarchar(128), 5 | LAST_NAME nvarchar(128), 6 | ); 7 | 8 | create table COLUMN ( 9 | ID varchar(36) primary key, 10 | NAME nvarchar(128), 11 | ); 12 | create table CARD ( 13 | ID varchar(36) primary key, 14 | TITLE nvarchar(256), 15 | DESCRIPTION clob, 16 | CREATION_TIME timestamp, 17 | CREATOR_ID varchar(36) not null, 18 | ASSIGNEE_ID varchar(36), 19 | COLUMN_ID varchar(36), 20 | ); -------------------------------------------------------------------------------- /src/test/java/io/qala/javabeginner/repository/memory/CardInMemoryRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package io.qala.javabeginner.repository.memory; 2 | 3 | import io.qala.javabeginner.domain.Card; 4 | import io.qala.javabeginner.domain.Column; 5 | import io.qala.javabeginner.domain.User; 6 | import io.qala.javabeginner.repository.CardRepository; 7 | import org.junit.Test; 8 | 9 | import java.util.List; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | public class CardInMemoryRepositoryTest { 14 | @Test 15 | public void savingCardAssignsId() { 16 | CardRepository repo = new CardInMemoryRepository(); 17 | Card newCard = cardInColumn(column()); 18 | 19 | repo.save(newCard); 20 | assertNotNull(newCard.getId()); 21 | } 22 | @Test 23 | public void savingCardLetsUsFindItByColumn() { 24 | CardRepository repo = new CardInMemoryRepository(); 25 | Column column = column(); 26 | Card newCard = cardInColumn(column); 27 | repo.save(newCard); 28 | 29 | List todoCards = repo.findByColumn(column); 30 | assertEquals(1, todoCards.size()); 31 | assertSame(newCard, todoCards.get(0)); 32 | } 33 | @Test 34 | public void ifNoCardsInColumn_thenEmptyCardListIsReturned() { 35 | CardRepository repo = new CardInMemoryRepository(); 36 | Card newCard = cardInColumn(column()); 37 | repo.save(newCard); 38 | 39 | List todoCards = repo.findByColumn(column()); 40 | assertEquals(0, todoCards.size()); 41 | } 42 | 43 | private static Column column() { 44 | Column column = new Column("TODO"); 45 | new ColumnInMemoryRepository().save(column); 46 | return column; 47 | } 48 | 49 | private static Card cardInColumn(Column column) { 50 | return new Card("title", new User("creator"), column); 51 | } 52 | } --------------------------------------------------------------------------------