This section will describe how the ANET Application Server works and the various components. Two major frameworks that we use are:
- Dropwizard is a framework for developing java-based web applications. It is a collection of other industry standard, open source, libraries to handle things like JSON parsing, HTTP multi-threading, Authentication, Authorization, Database access, etc. We highly recommend you read the getting started guide and briefly scan the user manual.
- GraphQL is a query language developed by Facebook to allow API consumers to explore a 'Graph' without requiring the server to have implemented every possible API call ahead of time. The client developer can ask for what data they want, and the GraphQL layer will figure out what data it needs to fetch and then fetch specifcally that data.
Throughout the next several sections, we were refer to the core ANET object types. These are:
- People
- Positions
- Organizations
- Reports
- Poams
- Locations
The Java Application Server is composed of 5 major pieces:
- Beans: Serializable Java objects that represent the major types in ANET (Person, Position, Organization, Report, Poam, etc). They are comprised of private fields typically representing the columns in the database, getters and setters for those fields, and
load*()
methods for the various relationships between object types (e.g. on the Person bean, there is aloadPosition()
method). - DAOs (Data Access Objects): These objects are the relationship between the beans and the SQL database. They contain all of the actual SQL statements to do the
INSERT
,SELECT
, andUPDATE
calls on a given object. There is one DAO per Bean (e.g.PersonDao
,PositionDao
,OrganizationDao
, etc), and that DAO returns objects (or Lists of Objects) of that particular type. So if you are looking for a Database Query that returns Reports, look inReportDao
. - Mappers: These are classes that store the logic on how to take a
ResultSet
from a SQL query and turn it into a Bean. They should be fairly straightforward and basic. There is one per Bean type. - Resources: These are classes that store all of the REST endpoints for a given object type. These methods are expected to take in an HTTP call from a user, and provide the correct response. In general, if you are trying to get an object of a particular type, you should be calling that Resource (e.g.
ReportResource
returns Reports,PersonResource
returns People). These methods can also be called by the GraphQL API if they are appropiately annotated. - SearchQuery: These classes are JavaBeans that represent the advanced searches that can be run on a particular core object type. There are then implementations for each object type's
SearchQuery
to actually perform the search. More on this later.
Here's a quick rundown of where to find each of these pieces:
Object Type | Bean | Dao | Mapper | Resource | SearchQuery |
---|---|---|---|---|---|
People | People.java | PersonDao.java | PersonMapper.java | PersonResource.java | PersonSearchQuery.java |
Positions | Positions.java | PositionDao.java | PositionMapper.java | PositionResource.java | PositionSearchQuery.java |
Organizations | Organizations.java | OrganizationDao.java | OrganizationMapper.java | OrganizationResource.java | OrganizationSearchQuery.java |
Reports | Reports.java | ReportDao.java | ReportMapper.java | ReportResource.java | ReportSearchQuery.java |
Poams | Poams.java | PoamDao.java | PoamMapper.java | PoamResource.java | PoamSearchQuery.java |
Locations | Locations.java | LocationDao.java | LocationMapper.java | LocationResource.java | LocationSearchQuery.java |
This is an attempt to describe the complete start to finish of how a request makes its way through the ANET stack.
- When you boot up ANET, it will execute the
mil.dds.anet.AnetApplication class
. All HTTP resources are initialized via therun()
method here. - Each resource is annotated with a
@Path
annotation that will tell you the URL path that the class will serve. - On boot, the server will spit out all of the HTTP paths that it knows about in the form:
GET / (mil.dds.anet.resources.HomeResource)
POST /advisorOrganizations/new (mil.dds.anet.resources.AdvisorOrganizationResource)
POST /advisorOrganizations/update (mil.dds.anet.resources.AdvisorOrganizationResource)
GET /advisorOrganizations/{id} (mil.dds.anet.resources.AdvisorOrganizationResource)
..... (continued) ....
- This tells you the URL, and then the class that will serve that URL.
- Parameters into those methods are autowired in based on the HTTP request
@PathParam
will pull a named parameter out of the@Path
annotation on the method (e.g./api/people/{id}
)@QueryParam
will pull a named parameter out of the URL query string (e.g.?foo=bar
)- Objects without annotations will be deserialized by Jackson from the HTTP entity
@Auth
will pull the current logged in user- If Authentication is required, the Auth filter in
mil.dds.anet.AnetAuthenticator
will determine your user principal. @Produces
annotations describe which content type the method can produce. This is almost always going to beapplication/json
.@GET
,@POST
, and similar annotations describe which HTTP methods this method will respond to.
- If Authentication is required, the Auth filter in
- Within each Resource class, there is a
dao
(Database Access Object). Each DAO contains all of the SQL logic for communicating with the database. - Each of the core ANET object types is represented by a bean that contains all of the properties and methods that that object can do. These are all in the
mil.dds.anet.beans
package.
- These objects are deserialized from JSON when they are passed in the HTTP request body as part of a POST
- These objects can be serialized back into JSON if they are returned by a method that is annotated with
@Produces(MediaType.APPLICATION_JSON)
@GET
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Report getReportById(@PathParam("id") int id) {
return dao.getById(id);
}
GraphQL is the mechanism by which most data is fetched to be displayed in the web-frontend. In ANET, we implemented GraphQL using the graphql-java implementation on top of our REST interface. By using GraphQL we call the exact same functions that the REST API does, but we are able to be much more descriptive about what data we want back, and therefore reduce the number of round-trips between the client and the server. Here is a brief run-through of how a graphql query gets executed:
- All GraphQL queries go to the
GraphQLResource#graphql()
method. This resource is initalized by being passed all of the other REST Resources within ANET. - The GraphQLResource will scan through all of the other REST Resources and look for methods that are annotated with the
@GraphQLFetcher
annotation. This annotation tells GraphQL that it can use this method as a "entry point" into the ANET Graph.
- "The Anet Graph" is the term we'll use to describe all of the data within ANET and the relationships between the different object types. e.g.: A Person with id 123 has a Position which belongs to an Organization... and so on.
- You can always pass the
f:
argument (f is for Function) to call a Resource method, this will use either the value passed to the@GraphQLFetcher
, or the name of the method if no value is passed.
- Once you have an object that was returned from a Resource (these are the Beans), you can call any of the 'get' methods on that object to fetch fields. To put this all together, the query
person(id:123) { id, name}
will look for a method on PersonResource that takes a parameter of nameid
, and then when it gets thePerson
object back, it will call thegetId()
andgetName()
methods on that Person object to fetch those fields. - Each of the primary objects within ANET (The Beans, or Person, Position, Organization, Report, Poam), knows about its relationships to other objects. Similar to the above example, if you query graphql with
person(id:123) { postion {name }}
it will find the person with id 123, and then call thegetPosition()
method on the Person object to fetch that relationship.
- This is an example of how GraphQL saves us a round-trip to the server versus using a strict REST api, where the client would have to call '/people/123' and then '/positions/XXX'.
- For any method where arguments are required, the GraphQLResource scans all of the methods in Resources and Bean classes to look for what arguments are required and then will look for those arguments passed via the GraphQL query.
- In a REST Resource, we use the existing
@PathParam
and@QueryParam
annotations to pull the name of the parameters. - In the Bean classes, we use
@GraphQLParam
to annotate the name of the parameters.
Here's a sample GraphQL query and how ANET processes it:
person(id:123) {
id,
name,
position {
id,
name,
},
authoredReports(pageNum:0, pageSize:10) {
id,
intent
}
}
- Because you POST'd this to
/graphql
, Dropwizard will take the JSON and deserialize it into aMap<String, Object>
and pass it to the GraphQL method. - The
graphql-java
library will parse the structure and validate the input. - The
person
keyword will tell GraphQL that you want to access theperson
object type at the root level. This goes to thePersonResource
. - The
AnetResourceDataFetcher
is the class that will get asked to load the person object. It looks at all of the methods onPersonResource
that are annotated with@GraphQLFetcher
and figures out which one has the right arguments to call. In this case it will begetById(int id)
. That method is called and it will do a database load and return the person. - GraphQL next looks at the fields on the Person that we need to load, in this case
id, name, position, authoredReports
. For each of these fields, they were defined on the person type by theGraphQLResource.buildTypeFromBean
method, which scans a Bean class for getter methods and wires them up to be used later. In this case it will look for methods calledgetId()
,getName()
. ForauthoredReports
andpositions
it will find the methodsloadAuthoredReports()
andloadPosition
because they are annotated with@GraphQLFetcher
.
- As a convention, methods starting with
get
will never perform a database load and always return the value currently in memory.load
functions will execute a database query if necessary in order to return the correct value.
- Each of these methods are called and the values returned.
- For
getAuthoredReports
, whenbuildTypeFromBean()
was scanning the Person Bean it noticed that this method required arguments, so it kept track of those. When you try to call this method it will inspect the query to see if you passed the correct arguments. If so it will pass those arguments, and if not then it will throw an error.
- Bottom line: All
get*()
methods on Beans are exposed. If they require arguments, you MUST annotate them with@GraphQLParam
. - You can use
@GraphQLIgnore
to tell GraphQL to not expose a getter method.
- Note: GraphQL does not use Jackson to serialize anything into JSON, it individually fetches the exact fields the client requests and transforms those into JSON using its own type system.
- Note: While GraphQL does technically support writing data to the server through mutations, we do not have any of that implemented. You must still use the REST API to write any data to ANET.