Skip to content
Snippets Groups Projects
Commit 1629d77c authored by Daniel Gerhardt's avatar Daniel Gerhardt
Browse files

Import caching and event system dev docs from Wiki

parent d59d07aa
1 merge request!44Split up and rewrite documentation
......@@ -61,8 +61,5 @@ The current build status for the master branch:
## Further Documentation
Please see our [Wiki][Wiki] for an in-depth look at development topics such as [Caching][Caching] and [ARSnova's event system][Event-System].
[Wiki]: https://github.com/thm-projects/arsnova-backend/wiki
[Caching]: https://github.com/thm-projects/arsnova-backend/wiki/Caching
[Event-System]: https://github.com/thm-projects/arsnova-backend/wiki/Event-System
* [Caching](development/caching.md)
* [Event System](development/event-system.md)
# Caching
Please read about Spring Framework's [Cache Abstraction](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html) first.
## What should get cached?
The short answer: All data that is written once and read multiple times. In ARSnova, there is an inherent `1:n` relationship between teachers and students. This makes everything the teacher creates a candidate for caching. But there are more opportunities, like students' answers which are mostly written once and cannot be changed afterwards. Be aware though that in this case, once a new answer comes in, the cache has to be invalidated. With many students answering questions at the same time the effects of caching go away since the cache is invalidated all the time.
While caching provides an opportunity to greatly speed up the execution of various requests, it does come with a price: You have to think of all cases were the cached data might become stale.
## How to design your objects
Caching should only be used with domain objects, where the `hashCode` and `equals` methods are provided. This makes it easy to update or delete cache entries. As you recall from the documentation, cache keys are based on a method's parameters. If you use base objects like `String` or `Integer`, you will have to manually provide a key through the Spring Expression Language (SpEL). As you can see from the following example, such keys can become quite complicated:
```java
@Cacheable(value = "notverycacheable", key = "#p0.concat('-').concat(#p1).concat('-').concat(#p2)")
public ResultObject notVeryCacheable(String sessionId, String questionVariant, String subject) { ... }
```
Therefore, you should always work with domain objects like `Session`, `Question`, or even your own, newly defined objects:
```java
@Cacheable("verycacheable")
public ResultObject veryCacheable(Session session) { ... }
```
Be aware though that you need to carefully choose the fields which should be part of the `equals`/`hashCode`: In case of CouchDB, for example, it is not a good idea to use a document's `rev` field. Every time a document is updated, it gets a new `rev` which will make it _unequal_ to all its previous versions, making cache updates using `@CachePut` impossible.
[ARSnova's event system](https://github.com/thm-projects/arsnova-backend/wiki/Event-System) provides a useful way for fine-grained cache updates because the events contain all relevant domain objects. If you need to clear or update a cache based on one of ARSnova's events, you can use the `CacheBuster` class to add your annotations.
## Issues
Caching requires the use of Spring Proxies. This means that methods invoked using `this` ignore all caching annotations! They only work across object boundaries because Spring is only able to intercept calls if they are going through a Spring Proxy. This could only be solved using AOP, but we have no intention to support this in the near future.
There is one exception: Since the `databaseDao` bean needs to call several methods on the same object, we implemented a workaround that allows access to the bean's proxy. When `getDatabaseDao()` is called within the bean, its proxy is returned that should be used instead of `this`.
One last word of caution: Your code should not rely on the cache's existence, and you should keep expensive calls to a minimum: Do not hit the database multiple times even though you think further calls are served by the cache.
## List of cache entries and associated keys
Here is a list of all caches, their keys, and a short description.
Cache name | Key | Description
-----------|-----|------------
`skillquestions`| `Session` entity | Contains all questions for the specified session irrespective of their variant.
`lecturequestions` | `Session` entity | Contains all "lecture" variant questions for the specified session.
`preparationquestions` | `Session` entity | Contains all "preparation" variant questions for the specified session.
`flashcardquestions` | `Session` entity | Contains all "flashcard" variant questions for the specified session.
`questions` | `Question` entity | Contains single question objects.
`questions` | database id of question | Although it shares the name of the previously mentioned cache, it is in essence a different cache because the keys are different. This means that the same `Question` object might be associated with two different keys.
`answers`| `Question` entity | Contains single answer objects.
`learningprogress` | `Session` entity | Contains `CourseScore` objects to calculate the learning progress values for the specified session.
`sessions` | keyword of session | Contains sessions identified by their keywords.
`sessions` | database id of session | Although it shares the name of the previously mentioned cache, it is in essence a different cache because the keys are different. This means that the same `Session` object might be associated with two different keys.
`statistics` | -- | Contains a single, global statistics object.
# Event System
ARSnova's event system allows objects to act upon changes to any of ARSnova's data structures. For example, if a new question is created, a `NewQuestionEvent` is sent out and any object that is interested can receive the question itself along with meta data such as the relevant `Session`. Among other use cases, the event is used to notify clients via Web Socket.
Events offer a way to dynamically update data without the need to poll the database for changes. For instance, if clients are interested in the number of currently available questions, the respective Bean could initialize the number using the database, while keeping it up to date using events.
## How to send events?
A class is able to send events by implementing the `ApplicationEventPublisherAware` interface. It consists of one method to inject an `ApplicationEventPublisher`, which can then be used to send the events like so:
```java
publisher.publishEvent(theEvent);
```
where `theEvent` is an object of type `ApplicationEvent`. For ARSnova, the base class `NovaEvent` should be used instead. All of ARSnova's internal events are subtypes of `NovaEvent`.
_Note_: Events are sent and received on the same thread, i.e., it is a synchronous operation.
## How to receive events?
Events are received by implementing the `ApplicationListener<NovaEvent>` interface. The associated method gets passed in a `NovaEvent`, which is the base class of all of ARSnova's events. However, this type itself is not very useful. The real type can be revealed using double dispatch, which is the basis of the Visitor pattern. Therefore, the event should be forwarded to a class that implements the `NovaEventVisitor` interface. This could be the same class that received the event.
_Note_: If the class implementing the Visitor needs to have some of Spring's annotations on the event methods, like, for example, to cache some values using `@Cacheable`, the Listener and the Visitor must be different objects.
## How to create custom events?
Subclass either `NovaEvent` or `SessionEvent`. The former is for generic events that are not tied to a specific session, while the latter is for cases where the event only makes sense in the context of a session.
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment