Skip to content

Latest commit

 

History

History
107 lines (62 loc) · 23.4 KB

design_document.md

File metadata and controls

107 lines (62 loc) · 23.4 KB

Design Document

Specification

At the University of Toronto, students like to order from food trucks, such as the ones that are commonly found on St. George st. The goal of this app is to allow students to place online orders for food from these food trucks on campus. A system like this would make life easier for both students and food truck vendors, since it allows students to save time while allowing food truck vendors to better expedite their orders.

Each food truck has a unique menu that contains different types of items, including normal items such as burgers or fries, but also combo items such as burger combo with fries and drink. A customer might also choose to customize their order - for example, one customer might want extra lettuce inside their wrap, and another might not want any pickles in their burger. Furthermore, individual food trucks might do things like deals or discounts.

As for the food trucks themselves, there are many food trucks around campus, each with their own locations and opening hours. Some food trucks may be unavailable on certain days.

Customers using this app would want to be able to log into some sort of account, especially since they are disclosing their personal information within the app. They would want their payments and transactions to be secure and smooth. From within the app, they should be able to browse all food trucks, view menus, order food, and view their order history.

Vendors, on the other hand, would also want to be able to log into some sort of account to be able to manage their food truck, which is essentially an online shop. They should be able to modify their shop by adding or removing items, updating item information such as prices, discounts, or components of the item (such as removing combo pieces). They should be able to update their opening and closing times at will. They should also be able to view their current orders, and choose to accept and confirm orders or decline them. Finally, this product will be used in real time, with customers interacting with vendors in real time in a number of ways. Since ordering food should be a fast process, the interactions should be quick and easy for both sides.

Major Design Decisions

  • Use of a database - We decided to use a database for a number of reasons, the most important being the intrinsic nature of our program which we envisioned would have multiple users using it at the same time and accessing possibly the same data. For example, both customers and vendors may access the same menu in real time, and vendors must also interact with customers in real time (for example, via order confirmations).
  • Web app deployment - There were extensive discussions surrounding whether we should create a web app or a native app such as an Android app. We ultimately decided to create a web app since this made the most sense for the most people (not everyone uses Android, but anyone can use a web app).
  • Food Sharing - Within our original implementation, Foods did not belong to FoodTrucks and were instead shared over multiple FoodTrucks, with their prices and components being modifiable across FoodTrucks. The reasoning for this was that it would be easier to access, since all Foods could then be stored in the same location on the database. However, we ultimately discontinued this, since we noticed that this made the code very confusing, and we were constantly having to check whether or not a Food already existed. We ended up making Foods unique to individual FoodTrucks since this made more sense from a design perspective, and also reduced coupling.
  • JWT usage - We decided to use JWTs (JSON web tokens) to implement token-based authentication instead of using session-based authentication for all of the Users using our program. The major considerations for us were security as well as efficiency - both of which were better fulfilled through usage of JWTs. Especially, since JWT tokens are stored on the client side, this requires less server memory which improves the scalability of the platform. This is why it is common convention for web applications like ours to use JWT.
  • Hashing algorithm SHA512 - We utilized the SHA512 hashing algorithm for storing passwords in our database. First of all, we realized early on that hashing passwords would be very important for security purposes. We chose to use the SHA512 hashing algorithm, since it is generally considered to be one of the best hashing algorithms, even better than SHA256 which is commonly considered a strong and widely used hashing algorithm. It should be noted that hashing is not nearly enough for password protection since there are many algorithms that can break hashes - however, for our purposes hashing is already a decent step towards having some semblance of security within our system.

Clean Architecture

Diagram

Overall, we think we’ve adhered to clean architecture rather well. We think that this can be seen through just our package structure, where we’ve structured the packages by layer - which shows a high level view of how we’ve organized our program to adhere to clean architecture. In fact, we’ve redesigned our entire code many times over in order to better adhere to clean architecture. Our current iteration, although imperfect (as we’ll elaborate on later), covers much of the principles of clean architecture. We’ve strongly modeled the structure of our entire program after the classic concentric circles diagram associated with clean architecture - our entities layer encapsulates the enterprise business rules layer, our use case layer encapsulates the application business rules layer, our controller layer encapsulates the interface adapter layer, and we have a layer of presenters as well as a database on the most outer layer. Furthermore, we’ve made strong efforts to adhere to the dependency rule - which states that dependency must only point from outer to inner adjacent layers. Wherever we have to go from inner to outer layers, we’ve utilized the dependency inversion principle, which can easily be seen in our use case layer which accesses the database layer via accessing a Repository interface which is of the type that the use case is related to.

However, there are instances where we’ve failed to utilize clean architecture. An easy example of this is the fact that in some cases, outer layers depend on concrete implementations of inner layers that are not adjacent to each other. For example, our controller layer depends on concrete implementations of our entities layer. However, the reason for this is mostly because as far as we know, Spring is defined that way and we have no easy way of fixing it.

Furthermore, in some of our use cases, we’ve accessed concrete implementations of the database access layer. Earlier, it was explained that we took care of most of these relations via the dependency inversion principle and accessing a Repository interface. However, later in our project we realized that there were functions that the Repository interface did not include that some use cases actually needed (Singleton types?).

SOLID Design Principles

  • Single Responsibility Principle (SRP) - The SRP is a design principle that states that a class should only be serving a single purpose. In practice, what this means is that each class should only ever have a single reason to change. We’ve followed this principle quite closely via usage of input and output boundaries in each of our use cases, so that each use case is not concerned about how data is outputted. For example, the createFoodInteractor class is only concerned with creating a food in a repository, and delegates the outputting of the food as a JSONObject to the ObjectBoundary class we have created to handle this. It also doesn’t actually do the action of creating the actual Food object, and instead delegates this to the FoodRepository elsewhere.
  • Open - Closed Principle (OCP) - The OCP states that objects or entities should be open for extension but closed for modification. This allows for addition of new functionality without needing to modify existing code. It also prevents situations where a change in a single class requires changes to all dependent classes. We think we’ve been particularly diligent with following this principle throughout most of our project - we’ve consistently utilized interfaces to provide extra layers of abstraction. Even though our entire project has little to no examples of polymorphism, we’ve created abstracted layers for most things, something which can easily be seen in our use case layer. One place where we’ve failed to use the OCP, however, is within the entities layer where we’ve deliberately chosen not to have a layer of abstraction since we have so few entities to begin with. However, if we were to be extra diligent in utilizing the OCP, we could have made an extra layer of abstraction so that if we were to have, say for example, an new entity that extended on the features of the Addon entity but was still an Addon, this would be possible. We will elaborate more on our thoughts surrounding this in the section on the dependency inversion principle, but we ultimately felt that this was not of great importance.
  • Liskov Substitution Principle (LSP) - This principle essentially states that every subclass or derived class must be substitutable for their base/parent class. In total, our program only has 2 files that have parents (Vendor and Customer). The Vendor and Customer both have the parent class User, and are substitutable for the User class, as all the overridden methods accept the same input parameters, and return values of the same type, which adheres to the LSP.
  • Interface Segregation Principle (ISP) - The ISP asserts that clients should not be forced to depend on interfaces that they do not use. In practice, what this means is that interfaces should be more specific rather than generic. For example, if an interface contains 5 methods, and a class only needs to implement 4 of those interfaces, then this violates the ISP and the generic interface should instead be segregated. We believe we’ve remained true to the ISP very well due to the fact that we’ve been very liberal with creating many specific interfaces rather than utilizing generic interfaces. This can easily be seen in our business rules layer, where each interface is only implemented by a single use case class that is very specific to the interface. We’ve also split interfaces into multiple interfaces where it makes sense, an example being VendorRepository and CustomerRepository which both extend the UserRepository interface as well as the Repository interface. We’ve done this split because even though Vendors and Customers are both users and are similar in most ways, they extend the Repository interface in different ways (Vendor extends Repository, and Customer extends Repository).
  • Dependency Inversion Principle (DIP) - This principle states that high level modules should not depend on low-level modules, and both should actually depend on abstractions instead. Overall, we’ve tried our best to stay true to the DIP. Much of what we’ve talked about in the section about the OCP and the LSP also applies to the DIP, since they are very (but not completely) similar. However, there have been a few areas where we’ve broken this principle, most notably in the controller layer, where our controllers depend on concrete implementations of entities. We actually noticed this quite early on in our code, but we felt that the solution to this, which would entail wrapping/converting entities into JSONObjects (as we have done in most of the rest of our code as can be seen in the use case implementations) would be overly complicated and tedious to implement. We felt that at a small scale, this problem would not be too large.

Packaging Strategies

In general, we have packaged our program by layer. That is to say that the highest-level packages in our program reflect the different layers of architecture, which are the interface adapters, business rules, controllers, and entities. However, within the business rules layer, we decided to package by feature. To clarify, the overall program is packaged by layer, but we packaged classes within the business rules layer based on the features that they were related to.

To explain, we chose to package our overall program by layer because initially, it felt that it would be easier to navigate by layer, and it would also be easier to delegate tasks as certain people could be responsible for certain layers. However, as we worked, we noticed that the business rules layer was quickly becoming crowded and difficult to navigate. However, the rest of the program was not difficult to navigate, so we felt that this problem did not warrant changing the packaging structure of the entire program. Thus, we chose to only package the business rules by feature, since that would solve the issue without needing to refactor the packaging of the entire program.

We acknowledge that if we were to restart the entire project from scratch with the knowledge we have now, we would probably choose to package by feature due to lower coupling across packages and higher cohesion within packages. However, as of current, the hybrid packaging strategy we are using does not pose major problems.

Refactoring

We refactored our project multiple times throughout the course of this semester. We initially refactored our phase 0 design as it didn’t adhere to clean architecture at all, as we didn’t really understand those principles during phase 0. We refactored towards the end of phase 1 where we refactored our project structure and dependencies to allow for Spring packaging. This meant switching to a Maven project among other refactors.

Clean Architecture Refactoring: CSC207-UofT#31 Maven Refactoring:: CSC207-UofT#103 Entities Refactoring: CSC207-UofT#80

Design Patterns

There were many uses of different sorts of design patterns used during the construction of our application. The main uses of design patterns were to ensure our application would be a good scaling application with easy modification and understandable code.

The first such example of design pattern is the strategy pattern which was used to ensure that the algorithm used to hash a string or sign a web token was easily modifiable. In fact, even the database connector was written with the strategy design pattern. This means we could have easily swapped out the database if we wanted to.

When sending responses from the presenter to the view, we had to use the adapter design pattern to adapt the output type of the presenter such that the view would be able to display it to the UI. In our specific case, this meant converting an entity object into a response object.

Another example of a design pattern used in the project is the template design pattern, used to create the user repository classes. User repository classes were formed from 2 different interfaces which contained requirements for the user repository to implement.

There are more examples of factory design patterns in the code but it requires some preliminary knowledge on react.js and can be found in the front end code. A lot of the design patterns were added in this refactor push: CSC207-UofT#80

Progress Report

  • Stephanie - What I have been working on has changed multiple times since phase 1 as we redesigned our project a few times. However, I will focus on my contributions to our final program. I implemented the presenters that are used by our use case interactors and controllers and made corrections to the use cases so that they were related to the correct output boundaries. I also worked with Akash on testing all of the controllers with each use case using Postman. This involved debugging and making changes to the files related to each of these entities. I also contributed to the design of the user interface by drawing a mock up of what our application will look like, including the flow of screens that a typical user will navigate through. I believe one of my most significant pull requests was my implementation of the presenters as this allowed our controllers to make calls to our input boundaries and have them return the correct information through the output boundary interfaces that the presenters implement. In addition, I have included a link to one of the pull requests I made for corrections so that our controllers worked as this meant our front end could properly interact with the backend and our database. Presenters: CSC207-UofT#111 Controller Testing: CSC207-UofT#126

  • Jeffrey - Since phase 1, I’ve been continuing to update Singleton and Vendor use cases, doing code cleanup (such as reformatting), as well as designing the front end for the customer side. A significant pull request I made is pasted below. It’s significant because it contains the vast majority of the customer side UI. Even though I’ve made significant commits in Java, this one still felt like the most impactful since we really needed a UI and few people knew how to work with UIs. It also took me the longest to figure out. Nevertheless, I’ve also included a significant Java contribution, which is the implementation of all the Singleton use cases. This was significant since it was part of a major code restructuring we were doing at the time, where we were working to drastically reduce coupling via input and output boundaries. UI: CSC207-UofT#120 Singleton: https://github.com/CSC207-UofT/course-project-javajsk/pull/49/files

  • Vijay - After introducing the front end by beginning the development of the react application, I also proceeded to design different aspects of the front end including vendor side creation pages (for creating food/singleton/addon). Moreover I introduced the team to the plan of using postman to test our RESTApi (the swing boot backend). In addition, I worked through creating the mongodb cluster and its related framework layer database manager required for our repository cases. One of the more serious pull requests I have made is when I suggested converting our project into a maven project and thus restructuring everything in order for us to be able to use Spring boot. This can be found here: CSC207-UofT#103.

  • Akash - I spent a lot of time debugging the backend using Postman. This involved refactoring a lot of our endpoint access points and the controllers that were directly hooked up with Spring. In one instance, there was an issue with how we were passing request bodies into Spring. We were initially passing objects as parameters which meant we’d have to pass in a JSON representation of an object and Spring would create it automatically but that was causing some issues so I ended up passing just the JSON objects and manually parsing the objects. I also created endpoints in the backend such as the GetAddonTypes, ViewAllShops, ViewShop, ViewCustomer endpoints. This involved creating use cases in the correct entities, then, modifying the controller to create a new method with an endpoint that called the use case I had just made. The actual use case was pulling data from the database so I had to write some MongoDB in our concrete database gateway to facilitate the actual data gathering. This was crucial as our frontend makes a lot of calls to these and it makes coding the front end a lot easier as you have direct access to data which you use often(as opposed to parsing from other calls that we already had).

Endpoint Testing/Debugging: CSC207-UofT#117 New Endpoints: CSC207-UofT#125

  • Kathy - After phase 1, I’ve worked on multiple different aspects of our program. I helped with some of the refactoring we did with our program like creating use cases to include the input and output boundaries as well as the adding of documentation to the new code so that it was clear to everyone what the code did. After that I sketched up what the vendor side user interface would look like. I included notes on how the pages would flow between each other and also labeled some actions that happened on each page and their corresponding parts in our backend to help us stay organized as we implemented the front end. Later I also started helping with the unit testing of our program. I worked on the tests for the use cases of some entities and so I had to create some concrete implementations of our boundaries and repositories. With the nature of writing unit tests, I was also reviewing our code and making corrections as I went along. Unit Tests: CSC207-UofT#130 Use Cases: https://github.com/CSC207-UofT/course-project-javajsk/pull/93/files

  • Jeremy - The tasks I’ve been working on have changed multiple times, after phase 1. Although I was initially working on some of the entities and use cases, for the final design of our project, I not only helped give input to adhering our program to clean architecture, but also I spent a lot of time doing unit tests for the program, contributing to testing the methods in entities and use cases. Since we had many changes to the design of our program, I had to restructure a lot of my tests. Along the way, I suggested different ideas for testing our code correctly and testing the most lines of code, an instance of this is when Avinash and I started making local repositories and boundaries for testing. This simplified the problem of testing code that interacted with a database and allowed us to use JUnit5 for our unit tests, which is a testing framework that our team is comfortable with. I have stated the links of some significant pull requests for the unit testing below. Due to this significant time testing code, I also went through a lot of code and made corrections to the code. Entity Unit Tests: CSC207-UofT#107 Use Case Unit Tests: CSC207-UofT#134

  • Avinash - At the beginning of the project, I was tasked to work alongside my team helping to write our entities and seeing how our project would look like using clean architecture. For phase 0, I worked with my teammates to draw up our CRC cards and used this to see the direction in which our project was heading. After phase 0 and phase 1, we had to restructure our program a few times in order to correctly adhere to clean architecture. As a team, we all pitched our ideas on how the program should run and came to a conclusion together about the final project. After we got our final model, I mostly worked on back-end unit testing for our entities, use cases etc. Since there were 7 of us in the group, we delegated tasks based on back and front end coding. The testing proved to be a rigorous process since we could not rely on our live database at the time and had to improvise by making local repositories. As a consequence of constant testing, I also found it necessary to change some of our core code due to some bugs Local repositories: CSC207-UofT#116. Entity tests: CSC207-UofT#106 Use case tests: CSC207-UofT#133