SpeADL Dynamic Tutorial » Historique » Révision 12
« Précédent |
Révision 12/37
(diff)
| Suivant »
Anonyme, 14/10/2014 17:37
SpeADL for Dynamic Architectures Tutorial¶
- Contenu
- SpeADL for Dynamic Architectures Tutorial
This is a tutorial for SpeADL: understanding ecosystems and species, defining a simple ecosystem, composing species together with uses.
Objectives¶
The objective of this tutorial to understand the abstractions of species and uses.
We will create a Logging ecosystem that contains Logger species.
Then we will create a Banking ecosystem that contains Account species that need to log what happens.
Finally we will create a Bank ecosystem that compose Account species with Logger species thanks to the use abstraction.
Prerequisites¶
It is needed to understand the content of the SpeADL⁻ tutorial before doing this one.
Creating a New Project and Organisation¶
Create a Java project.
We will use again an organisation of the package and namespaces as explained in this best practice.
The Logging Ecosystem¶
Logging will be an ecosystem, and its particularity is that it will be able to create Loggers: while logging is the subsystem responsible of all logging, each logger will be responsible of logging one particular aspect of the system identified by a name.We will have multiple implementation for the Logging ecosystem:
- One with only one file where to log with each line prepended with the name.
- One with one file per Logger all in the same directory.
In any case, a port will be provided by Logging to create standalone instances of Logger.
Defining the Ecosystem and the Species¶
Create a SpeADL file named logging.speadl in the package tutorial2.logging.
Define in it the ecosystem and the species in the right namespace as well as the needed interface:
import tutorial2.logging.interfaces.ILog
namespace tutorial2.logging {
ecosystem Logging {
provides create: ICreateLogger
species Logger(name: String) {
provides log: ILog
}
}
}
package tutorial2.logging.interfaces;
public interface ILog {
public void addLine(String line);
}
public interface ICreateLogger {
public Logging.Logger.Component createStandaloneLogger(String name);
}
Implementing the Ecosystem and the Species, First One¶
Create a new Java class in tutorial2.logging.impl named LoggingImplOneFile that extends Logging, that takes the a File as a parameter to the constructor to store the logs, and resolve the error with the Quick Fixes of Eclipse:
package tutorial2.logging.impl;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import tutorial2.logging.Logging;
import tutorial2.logging.interfaces.ICreateLogger;
public class LoggingImplOneFile extends Logging {
private PrintWriter logWriter;
public LoggingImplOneFile(File logFile) {
// if an exception happens, nothing can be done about it
// we just let logStream be null and
// the operations of logging won't be done
try {
this.logWriter = new PrintWriter(new FileWriter(logFile), true);
} catch (FileNotFoundException e) {
System.err.println("An error happened with the file, nothing will be logged.");
this.logWriter = null;
} catch (IOException e) {
System.err.println("An error happened with the file, nothing will be logged.");
this.logWriter = null;
}
}
@Override
protected ICreateLogger make_create() {
// TODO Auto-generated method stub
return null;
}
@Override
protected Logger make_Logger(String name) {
// TODO Auto-generated method stub
return null;
}
}
As we can see, the species we defined needs to be implemented and this implementation needs to be returned by the method Logger make_Logger(String name).
Let's define an inner class inside LoggingImplOneFile to do that, it will take as a parameter to the constructor the name of the Logger and that will exploit the logWriter to do the actual logging.
public class LoggingImplOneFile extends Logging {
private PrintWriter logWriter;
// ...
@Override
protected Logger make_Logger(String name) {
return new LoggerImpl(name);
}
private class LoggerImpl extends Logger implements ILog {
private final String name;
public LoggerImpl(String name) {
this.name = name;
}
@Override
protected ILog make_log() {
return this;
}
@Override
public void addLine(String line) {
if (logWriter != null) {
logWriter.println("["+name+"] "+ line);
}
}
}
}
Then let's implement the create port by exploiting the method newLogger(String name) present in the extended class that enables to instantiate a species:
public class LoggingImplOneFile extends Logging {
// ...
@Override
protected ICreateLogger make_create() {
return new ICreateLogger() {
@Override
public Logger.Component createStandaloneLogger(String name) {
return newLogger(name);
}
};
}
// ...
}
We now have a complete implementation for the Logging ecosystem.
Let's test it with a very simple program that we put in the package tutorial2.logging.tests*:
package tutorial2.logging.tests;
import java.io.File;
import tutorial2.logging.Logging;
import tutorial2.logging.Logging.Logger;
import tutorial2.logging.impl.LoggingImplOneFile;
public class LoggingTest {
public static void main(String[] args) {
Logging.Component logging = new LoggingImplOneFile(new File("/tmp/tutorial2-logging-test.txt")).newComponent();
// create new Loggers
Logger.Component l1 = logging.create().createStandaloneLogger("a");
Logger.Component l2 = logging.create().createStandaloneLogger("b");
Logger.Component l3 = logging.create().createStandaloneLogger("c");
// log things to them
l1.log().addLine("1 a says hi");
l1.log().addLine("2 test test");
l2.log().addLine("3 b says hoy");
l1.log().addLine("4 blabla");
l3.log().addLine("5 hop");
}
}
Execute it and confirm that the file /tmp/tutorial2-logging-test.txt contains the following:
[a] 1 a says hi [a] 2 test test [b] 3 b says hoy [a] 4 blabla [c] 5 hop
Implementing the Ecosystem and the Species, Second One¶
We can now define a second implementation that stores the logs of each Logger in its own file:
package tutorial2.logging.impl;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import tutorial2.logging.Logging;
import tutorial2.logging.interfaces.ICreateLogger;
import tutorial2.logging.interfaces.ILog;
public class LoggingImplDirectory extends Logging {
private final File logDir;
public LoggingImplDirectory(File logDir) {
this.logDir = logDir;
this.logDir.mkdirs();
}
@Override
protected Logger make_Logger(String name) {
return new LoggerImpl(new File(logDir, name+".txt"));
}
@Override
protected ICreateLogger make_create() {
return new ICreateLogger() {
@Override
public Logger.Component createStandaloneLogger(String name) {
return newLogger(name);
}
};
}
private class LoggerImpl extends Logger implements ILog {
private PrintWriter logWriter;
public LoggerImpl(File logFile) {
// if an exception happens, nothing can be done about it
// we just let logStream be null and
// the operations of logging won't be done
try {
this.logWriter = new PrintWriter(new FileWriter(logFile), true);
} catch (FileNotFoundException e) {
System.err.println("An error happened with the file, nothing will be logged.");
this.logWriter = null;
} catch (IOException e) {
System.err.println("An error happened with the file, nothing will be logged.");
this.logWriter = null;
}
}
@Override
protected ILog make_log() {
return this;
}
@Override
public void addLine(String line) {
if (logWriter != null) {
logWriter.println(line);
}
}
}
}
It can be tested with the previous class by changing the first line instantiating the component:
Logging.Component logging = new LoggingImplDirectory(new File("/tmp/tutorial2-logging-test/")).newComponent();
Execute it and confirm that the /tmp/tutorial2-logging-test/ directory contains one file per Logger with the correct content.
The Banking Ecosystem¶
Banking will also be an ecosystem, it will contain Account that are species with a particularity: they have required ports!
Because of that, they can't simply be created but must be composed with other components or species to be usable.
Defining the Ecosystem and the Species¶
Create a SpeADL file named banking.speadl in the package tutorial2.banking and define a Banking ecosystem in tutorial2.banking namespace:
import tutorial2.banking.interfaces.IAccountOperations
import tutorial2.banking.interfaces.IAccountStatus
import tutorial2.logging.interfaces.ILog
namespace tutorial2.banking {
ecosystem Banking {
species Account(owner: String) {
provides operations: IAccountOperations
provides status: IAccountStatus
requires log: ILog
}
}
}
Then the interfaces in tutorial2.banking.interfaces:
package tutorial2.banking.interfaces;
public interface IAccountOperations {
public void deposit(int value);
public void withdraw(int value);
}
public interface IAccountStatus {
public int getBalance();
}
Implementing the Ecosystem and the Species¶
We now can define an implementation named BankingImpl in the package tutorial2.banking.impl.
It exploits the provided ports and the required ports of the species, but also the required port of the ecosystem:
package tutorial2.banking.impl;
import java.util.concurrent.atomic.AtomicInteger;
import tutorial2.banking.Banking;
import tutorial2.banking.interfaces.IAccountOperations;
import tutorial2.banking.interfaces.IAccountStatus;
public class BankingImpl extends Banking {
@Override
protected Account make_Account(final String owner) {
return new Account() {
private final AtomicInteger balance = new AtomicInteger();
@Override
protected void start() {
eco_requires().elog().addLine("Added a new account for "+owner);
}
@Override
protected IAccountOperations make_operations() {
return new IAccountOperations() {
@Override
public void withdraw(int value) {
provides().operations().deposit(-value);
requires().log().addLine(value+" were withdrawn, current balance: "+provides().status().getBalance());
}
@Override
public void deposit(int value) {
balance.addAndGet(value);
requires().log().addLine(value+" were deposited, current balance: "+provides().status().getBalance());
}
};
}
@Override
protected IAccountStatus make_status() {
return new IAccountStatus() {
@Override
public int getBalance() {
return balance.get();
}
};
}
};
}
}
Notice the calls to provides() and requires().
Now let's add a bit more log for when a species is instantiated: it must be logged in a file specific to the ecosystem and not to the species.
Let's add a required port to the ecosystem Banking:
requires elog: ILog
Then let's add a start() method to the implementation of Account in BankingImpl:
@Override
protected void start() {
eco_requires().elog().addLine("Added a new account for "+owner);
}
Notice the use of eco_requires() from within the species to access the ports of the ecosystem.
Composing with Uses into a Bank Application¶
Now that we have the two functionality we need, we can build our application to compose them together.
In order to do that, we need to define an ecosystem with a species that "uses" the other two.
Defining the Ecosystem and the Species¶
Let's define the ecosystem Bank in a SpeADL file named bank.speadl in the package and namespace tutorial2.bank:
import tutorial2.banking.Banking
import tutorial2.banking.interfaces.IAccountOperations
import tutorial2.banking.interfaces.IAccountStatus
import tutorial2.logging.Logging
import tutorial2.logging.interfaces.ILog
namespace tutorial2.bank {
ecosystem Bank {
provides elog: ILog
part l: Logging
part b: Banking {
bind elog to elog
}
species LoggedAccount(owner: String) {
provides operations: IAccountOperations = ba.operations
provides status: IAccountStatus = ba.status
use ll: l.Logger(owner)
use ba: b.Account(owner) {
bind log to ll.log
}
}
}
}
Notice the following points:
- Each of the ecosystem that are needed are declared as parts of the Bank ecosystem, and the species we defined in them are declared as uses of the species LoggedAccount.
- In order to bind the required port of b: Banking, the containing ecosystem needs to provide a port that will be used in the binding. This port is not meant to be used from the exterior but from inside the ecosystem.
- The required ports of the Account species are provided by the Logger species.
The idea behind such a composition is that when an instance of LoggedAccount is created, then it implies that an instance of Logger and an instance of Account are created at the same time and used as parts of LoggedAccount.
Each of the species retains its links to its own ecosystem without leaking it to the outside.
Implementing the Ecosystem and the Species¶
Implementing the ecosystem is quite straightforward as there is not so much things to add in the implementation.
Let's create BankImpl in the package tutorial2.bank.impl:
package tutorial2.bank.impl;
import tutorial2.bank.Bank;
import tutorial2.banking.Banking;
import tutorial2.logging.Logging;
import tutorial2.logging.interfaces.ILog;
public class BankImpl extends Bank {
@Override
protected ILog make_elog() {
// TODO Auto-generated method stub
return null;
}
@Override
protected Logging make_l() {
// TODO Auto-generated method stub
return null;
}
@Override
protected Banking make_b() {
// TODO Auto-generated method stub
return null;
}
@Override
protected LoggedAccount make_LoggedAccount(String owner) {
// TODO Auto-generated method stub
return null;
}
}
First, let's fill the methods for the parts:
public class BankImpl extends Bank {
private final File logDir;
public BankImpl(File logDir) {
this.logDir = logDir;
}
@Override
protected Logging make_l() {
return new LoggingImplDirectory(logDir);
}
@Override
protected Banking make_b() {
return new BankingImpl();
}
// ...
}
Then let's fill the method for the species.
As the species has nothing apart from uses and already delegated provided ports, there is no implementation to do:
public class BankImpl extends Bank {
// ...
@Override
protected LoggedAccount make_LoggedAccount(String owner) {
return new LoggedAccount() {};
}
// ...
}
Finally, let's use the Logging component to get a special Logger for the whole ecosystem:
public class BankImpl extends Bank {
// ...
private Logging.Logger.Component eLogger;
@Override
protected void start() {
eLogger = parts().l().create().createStandaloneLogger("BANK");
}
@Override
protected ILog make_elog() {
return new ILog() {
@Override
public void addLine(String line) {
eLogger.log().addLine(line);
}
};
}
// ...
}
Notice for that last one that because the make_elog() method is called during initialisation of the component before the start() method, we have to explicitely delegate the methods of the ILog interface and can't just return eLogger.log() as the imlementation of the port elog.
The eLogger class member must be ininialised in the start() method because parts() won't work properly before.
Mis à jour par Anonyme il y a plus de 11 ans · 37 révisions