Architektur

Hauptmerkmale einer Softwarearchitektur

Eine Softwarearchitektur beschreibt eine Kollektion aus monolithisch aufgebauten Komponenten, die gemeinsam zur Bewältigung ihrer Anforderungen benötigt werden. Zusätzlich beschreibt sie die Schnittstellen der einzelnen Komponenten und die Regeln der Interaktionen untereinander.
Einfach gesagt ist es ein abstrakter Blick auf die gesamte Software, ohne hierbei innere Details der einzelnen Komponenten aufzuzeigen.

Aufgaben trennen

Die Hauptaufgabe einer Architektur ist eine gut strukturierte Trennung der einzelnen Aufgabenbereiche.

In meinen Architekturen achte ich sehr auf die strikte Aufgabentrennung zwischen der Geschäftslogik und den technischen Bereichen. Was das genau bedeutet, möchte ich anhand eines kleinen Beispieles aus meinem privaten Softwareprojekt buttermilk aufzeigen. Zuerst stelle ich vereinfacht das Big Picture dar, danach zeige ich zu den jeweiligen Bereichen entsprechende Code-Beispiele. Das gesamte Code-Beispiel zeigt die Funktion zum Erstellen eines neuen Benutzers im Service Benutzer.

Bild einer Architektur

Big-Picture aus dem Projekt buttermilk.

1.) Boundary

@Path(PathDeclaration.USER_SERVICE)
@Stateless
public class UserServiceBean implements UserService {
	@EJB
	private UserBean userBean;

	@Override
	@LastLineOfDefense
	@POST
	@Produces(ContentType.APPLICATION_JSON_TRANSPORTER)
	@Consumes(ContentType.APPLICATION_JSON_TRANSPORTER)
	@Path(PathDeclaration.USER_SERVICE_CREATE)
	public Transporter createUser(Transporter transporter) {
		User user = transporter.getValue(User.class);
		userBean.create(user);

		return new Transporter();
	}

Die Boundary ist die Schnittstelle nach außen.
Aufgaben:

  • Vereinbarung über URL und Methodik für den REST Aufruf (Zeile 1, 9 und 12).
  • marshal/unmarshal der Objekte von Java zu JSON und umgekehrt (Zeile 10 und 11). Hier mit Annotation/Producer umgesetzt, so dass Entwickler innerhalb der Methode bequem auf Java Ebene mit dem vereinbarten Objekt „Transporter“ weiter arbeiten können.
  • Last Line of Defense (in Punkt 2 genauer erklärt, Zeile 8).
  • Session Handling (in diesem Beispiel nicht benötigt, im gesamten Projekt auch durch eine Annotation realisiert).
  • Aufruf zur Abarbeitung der Geschäftslogik (Zeile 15).
  • Antwort an den Aufrufer (Zeile 17).

2.) Last Line of Defense

	...
	@LastLineOfDefense
	public Transporter createUser(Transporter transporter) {

Die letzte Linie der Verteidigung, damit nur vereinbarte Ausnahmen über diese Schnittstelle nach außen gelangen.
Aufgaben:

  • Alle Ausnahmen abfangen, auswerten und gegebenenfalls aufbereiten (Zeile 2). Realisiert durch eine Annotation, damit Entwickler der Boundary sich nicht weiter um Ausnahmebehandlungen kümmern müssen.

3.) Core

@Stateless
public class UserBean {
	@Inject
	private Is is;
	
	@EJB
	private UserPersistenceBean userPersistenceBean;
	
	@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
	public void create(User user) {
		if (user == null) {
			throw new RemoteException(DictionaryExceptionId.USER_SERVICE_USER_OBJECT_NULL);
		}
		if (is.empty(user.getId())) {
			throw new RemoteException(DictionaryExceptionId.USER_SERVICE_USER_ID_NULL);
		}
		if (is.empty(user.getNick())) {
			throw new RemoteException(DictionaryExceptionId.USER_SERVICE_USER_NICK_EMPTY);
		}
		if (is.empty(user.getPassword())) {
			throw new RemoteException(DictionaryExceptionId.USER_SERVICE_USER_PWD_EMPTY);
		}
		if (is.empty(user.getSalt())) {
			throw new RemoteException(DictionaryExceptionId.USER_SERVICE_USER_SALT_EMPTY);
		}
		if (is.empty(user.getEmail())) {
			throw new RemoteException(DictionaryExceptionId.USER_SERVICE_USER_EMAIL_EMPTY);
		}
		if (userPersistenceBean.exists(user.getEmail())) {
			throw new RemoteException(DictionaryExceptionId.USER_SERVICE_USER_DUPLICATE);
		}
		user.setCreated(new Date());
		user.setPassword(pepperPassword(user.getPassword()));
		userPersistenceBean.create(user);
		logger.info(String.format("User %s created", user.getNick()));
	}

Der Core Bereich ist in der Regel der am häufigsten geänderte Bereich und gehört daher strikt gekapselt. Hier wird die Logik des Kundenwunsches ausgearbeitet. Allerdings bitte nur die pure Geschäftslogik, nichts technisches wie zum Beispiel das Aufbereiten von Ausnahmen oder gar das Definieren von SQL/JPQL Statements.
Je weniger „technischer Code“ hier anzutreffen ist, desto schneller ist jeder Kundenwunsch umgesetzt.
Aufgaben:

  • Abarbeitung der Geschäftlogik (Zeile 11-33).
  • Aufruf zur Persistierung des Benutzers (Zeile 34).

4.) Persistence Layer

public abstract class AbstractPersistenceBean {
	private final Class entityClass;
	protected abstract EntityManager getEntityManager();
	
	public AbstractPersistenceBean(Class entityClass) {
		this.entityClass = entityClass;
	}

	public void create(T entity) {
		getEntityManager().persist(entity);
	}

Der Persistence Layer ist die Schnittstelle zur Datenbank.
Aufgaben:

  • Persistieren, Abrufen oder Aktualisieren der Daten. In diesem einfachen Beispiel liegt die Funktionalität „create“ direkt in der abstrakten Klasse. Für komplexe Aufgaben besitzt jedes Objekt seine eigene abgeleitete Persistierungs-Einheit, um auch diese Aufgabenbereiche von einander zu trennen.

Verwalter der Architektur

In kleinen Softwareprojekten reicht es meist, einen sehr erfahrenen Softwareentwickler als Architekten einzusetzen. Bei größeren Softwareprojekten ist es für einen Architekten unmöglich, alle die an ihn gestellten Anforderungen zu erfüllen. Die Architektur sollte dann besser von einer Gruppe aus Top-Entwicklern gemeinsam entworfen und erweitert werden.

Das Architekten-Team sollte sich wie folgt zusammensetzen.
Aus jeder Abteilung mindestens ein Entwickler, der auch tagtäglich diese Architektur-Komponenten in seinem Bereich verwendet. Dieses garantiert bei jeder Änderung an der Architektur einen gewissen Weitblick über die gesamte Software. Zudem ist die Aufteilung der Last für jeden einzelnen Entwickler aus diesem Team wesentlich geringer, was der Kreativität zu Gute kommt, die hier sehr gefragt ist.

Einen sog. Master-Architekten muss es trotzdem geben. Es handelt sich bei dieser Position allerdings mehr um eine leitende, als um eine klassische Architekten Tätigkeit. Er sollte zudem nur noch bei Uneinigkeit entscheiden.