Refactoring decisions - Pyosch/powertac-server GitHub Wiki

up

This page will record specific decisions, processes, and conventions regarding server refactoring.

Project and directory layout

The refactored server will be contained entirely in the github "Powertac" project. The "powertac-plugins" project on github is deprecated.

Within a server component, Spring does not enforce the distinctions among domain objects, services, controllers, etc. as in Grails. These are useful for understanding the system, and therefore we will divide implementation among source directories called domain, services, test, and others that might make sense. A directory called "src" is equivalent to "uncategorized" and therefore there will no directory called "src".

In powertac-common, we have two packages org.powertac.common.command and org.powertac.common.msg. The types in ..msg are domain types (they are stored in the database), but the types in ..command are just messages. We will merge these into domain/org/powertac/common/msg.

The functionality of the grails plugin powertac-random needs to be split up, to better support testing. The domain type is moved to server-interface, while the service will be folded into the top-level module powertac-server. There is no strong justification for keeping powertac-random as a separate module, since it's unlikely to be replaced for research purposes. The random seed functionality needs to be mocked for most testing purposes, so there's no clear dependency-related reason to put it in server-interface.

Converting from Groovy to Java

Process, experiences and tips on the process of converting code from Groovy to Java.

Use of Spring auto-wiring capabilities

Spring offers two types of code annotation that can greatly simplify the process of finding and injecting object references into a modular system like the Power TAC server:

  • @Autowired annotation -- If you label a setter method, constructor, or instance variable with @Autowired, the server will attempt to find a matching component (normally a "service"), instantiate it if needed, and inject the reference.

  • Auto-scan -- If you set up the "component-scan" feature in the server's config file, then Spring will attempt to discover autowire candidates by looking for classes that are annotated with @Service or one of a few other tags.

Core vs. replaceable modules

The use of @Autowired makes great sense, and reduces the need for detailed coordination between code and configuration. It will be used universally in the Power TAC server to the extent possible. The auto-scan feature is more problematic, because many components are intended to be replaceable, such as the market and customer models. So if there were three market implementations on the classpath annotated with @Service, the server would encounter an error on startup. Therefore, the auto-scan feature can be used only for services that are highly unlikely to ever be replaced, such as factory-finder implementations. Replaceable services must be specified in the server's configuration file with <bean> clauses.

Autowiring and domain types

Autowiring only works for classes that are listed as "beans" in the Spring config file, or for classes that are annotated as @Service, @Repository, etc. Usually these are singletons, and are created by the platform. On the other hand, most domain types (Tariffs, Tariff Subscriptions, Orderbooks, etc.) are not services and are definitely not singletons. Therefore, they do not get autowired by Spring. In Grails they did, sometimes, so we had code to access the application context. This is not so easy in Spring/Java. So I have created a service common/services/../SpringApplicationContext that includes a static method for retrieving service references. You can see a usage example in common/domain/../Tariff, and another one in server-interface/domain/../TariffSubscription.

Factory-finder implementations

Location of factory-finders -- Most domain types are in common, because they are shared between server and broker. However, it's not clear that the storage needs for broker and server are the same at all. Unfortunately, there are several cases where xml deserialization depends on looking up implementations from identifiers. This would seem to demand that the factory-finder implementations be bundled with the domain types, and not in server-interface.

Numeric types

The grails version makes considerable use of BigDecimal, Long, and Integer types. BigDecimal will be replaced by double, and the others will be converted to the corresponding primitive types, except that the float type will not be used at all. Because of numeric precision issues with double, there are a few cases where an epsilon parameter must be added to assertEquals tests in test cases, that were not needed in the BigDecimal case.

Object identifiers

John and Prashant discussed this issue on 8/23. Both of us are not thrilled with the use of UUIDs, because they convey no useful information, and they are long. We don't need UUIDs; we need identifiers that are unique within the context of a game, not universally. Therefore, ID values will be longs, the sum of a prefix and a sequence number. Each process entity in a game environment that could potentially generate domain entities (server, brokers, possibly remote customer models) will be assigned a prefix by the server. The new IdGenerator in common implements this scheme.

Component naming

The "powertac" prefix will be dropped from component names (these are also github repo names). They are already inside a project called "powertac".

Random number sequences

The generation and use of RandomSeed instances has been greatly simplified:

  • The powertac-random module is eliminated; its functionality is now folded into server-interface.
  • RandomSeed is now a subclass of java.util.Random so there is no longer a need to acquire a new Random instance using the seed value. Just call nextLong() or nextDouble() or nextGaussian() or whatever directly on the RandomSeed instance.
  • RandomSeedService is eliminated, replaced by the new org.powertac.common.repo.RandomSeedRepo. The renaming is an indication that the functionality is fairly different. New RandomSeed instances are acquired through RandomSeedRepo.getRandomSeed(). Because this is a 'normal' repo, it will be recycled at the end of a game. RandomSeed instances are logged in the state log, and an existing state log can be read to re-load the random seeds from an old game by calling RandomSeedRepo.loadSeeds(). In this case, calls to getRandomSeed() will return the seeds from that game.
⚠️ **GitHub.com Fallback** ⚠️