Commit 3e1bd5ea authored by Jordan Breton's avatar Jordan Breton

Added : auto inconsistency check for models (they are automaticaly rebuilt if...

Added : auto inconsistency check for models (they are automaticaly rebuilt if any race condition result in an event applyed after another that have been generated after)
Added : Base layout for websites to speed up project creation removing boilerplate code.
Version bumped.
parent e24731ba
......@@ -33,6 +33,8 @@ use wfw\daemons\modelSupervisor\server\responses\RequestError;
use wfw\daemons\modelSupervisor\socket\data\MSServerDataParserResult;
use wfw\daemons\modelSupervisor\socket\protocol\MSServerSocketProtocol;
use wfw\daemons\multiProcWorker\Worker;
use wfw\engine\core\data\model\errors\InconsistentModel;
use wfw\engine\core\domain\events\observers\errors\ListenersFailedToRecieveEvent;
use wfw\engine\lib\cli\signalHandler\PCNTLSignalsHelper;
use wfw\engine\lib\data\string\serializer\ISerializer;
use wfw\engine\lib\logger\ILogger;
......@@ -510,7 +512,28 @@ final class WriterWorker extends Worker {
if($clientRequest instanceof ApplyEvents){
/** @var \wfw\engine\core\domain\events\EventList $events */
$events = $this->_serializer->unserialize($request->getData());
$this->_workerParams->getModelManager()->dispatch($events);
try{
$this->_workerParams->getModelManager()->dispatch($events);
}catch(ListenersFailedToRecieveEvent $e){
$toRebuild = [];
foreach($e->getReports() as $report){
if($report->getError() instanceof InconsistentModel)
$toRebuild[] = get_class($report->getListener());
else $this->_environment->getLogger()->log(
"[WRITER] [WORKER] $e",ILogger::ERR
);
}
if(count($toRebuild) > 0){
$this->_workerParams->getModelSynchronizer()->synchronize(
...$toRebuild
);
$this->_environment->getLogger()->log(
"[WRITER] [WORKER] Following models have been rebuilt beacause of inconsistent state : "
.implode(", ",$toRebuild),
ILogger::ERR
);
}
}
$this->sendResponse(new WriterResponse(
$request->getQueryId(),
new DoneResponse()
......
......@@ -2,7 +2,7 @@
"server": {
"framework": {
"name": "wfw",
"version": "STABLE-1.3"
"version": "2.0"
},
"daemons": {
"kvs": "kvstore\/server\/config\/conf.json",
......@@ -15,9 +15,9 @@
"updator": "updator\/config\/conf.json"
},
"language": {
"default": "fr",
"default": "fr_FR",
"availables": [
"fr"
"fr_FR"
]
},
"router": {
......
......@@ -2,6 +2,7 @@
namespace wfw\engine\core;
use wfw\engine\core\app\context\WebAppContext;
use wfw\engine\core\app\WebApp;
/**
......@@ -15,6 +16,7 @@ class Main {
*/
public function __construct(array $args=[]){
$contextInfos = (require dirname(__DIR__,2)."/site/config/site.context.php")($args);
new WebApp(new $contextInfos["class"](...($contextInfos["args"] ?? [])));
$class = $contextInfos["class"] ?? WebAppContext::class;
new WebApp(new $class(...($contextInfos["args"] ?? [])));
}
}
\ No newline at end of file
......@@ -48,7 +48,6 @@ final class UserTypeBasedCommandAccessRule implements ICommandAccessRule {
* @param ICommandAccessRuleFactory $factory
* @param ICacheSystem $cache
* @param array $permissions
* @throws \InvalidArgumentException
*/
public function __construct(
IUserModelAccess $model,
......
......@@ -44,4 +44,13 @@ class EventReceptionReport {
public function getRemoved():array{
return $this->_removed;
}
/**
* @return bool
*/
public function modelChanged():bool{
return count($this->_modified) > 0
|| count($this->_removed) > 0
|| count($this->_created) > 0;
}
}
\ No newline at end of file
<?php
namespace wfw\engine\core\data\model;
use wfw\engine\core\data\model\errors\InconsistentModel;
use wfw\engine\core\domain\events\IDomainEvent;
use wfw\engine\core\data\model\DTO\IDTO;
use wfw\engine\core\data\specification\ISpecification;
......@@ -19,6 +20,8 @@ abstract class InMemoryEventBasedModel implements IEventListenerModel {
private $_searcher;
/** @var int $_length */
private $_length;
/** @var IDomainEvent[][] $eventHistory */
private $_eventHistory = [];
/**
* Repository constructor.
......@@ -186,6 +189,21 @@ abstract class InMemoryEventBasedModel implements IEventListenerModel {
*/
public final function recieveDomainEvent(IDomainEvent $e): void {
$report = $this->recieve($e);
if($report->modelChanged()){
$id = (string) $e->getAggregateId();
if(!isset($this->_eventHistory[$id])) $this->_eventHistory[$id] = [];
$eventClass = get_class($e);
if(!isset($this->_eventHistory[$id][$eventClass])) $this->_eventHistory[$id][$eventClass] = $e;
$lastEvent = $this->_eventHistory[$id][$eventClass];
if($lastEvent !== $e && $e->getGenerationDate() < $lastEvent->getGenerationDate()){
throw new InconsistentModel(
static::class." gone in inconsistent state due to a non chronological event applyance order. This"
." may happen because of a race condition.\nAggregate : ".$e->getAggregateId()."\nEvent $eventClass (id "
.$e->getUUID().")\nLastEvent date : ".$lastEvent->getGenerationDate()
." (id : ".$lastEvent->getUUID().")"
);
}
}
foreach ($report->getCreated() as $obj){
$this->add($obj);
}
......
<?php
namespace wfw\engine\core\data\model\errors;
/**
* Thrown when a model gone ine an inconsistent state.
*/
class InconsistentModel extends ModelFailure {}
\ No newline at end of file
<?php
namespace wfw\engine\core\data\model\errors;
/**
* Thrown while a model failed in some way.
*/
class ModelFailure extends \Exception {}
\ No newline at end of file
......@@ -53,7 +53,7 @@ final class KVSBasedModelStorage implements IModelStorage {
* @param int $storageMode
*/
public function set(string $key, IModel $model, ?int $storageMode = null) {
$this->_access->set($key,$model,0,$storageMode??$this->_defaultStorage);
$this->_access->set($key,$model,0,$storageMode ?? $this->_defaultStorage);
}
/**
......
......@@ -20,6 +20,8 @@ interface IModelSynchronizer {
/**
* Lance la synchronisation
*
* @param string[] $modelsToRebuild
*/
public function synchronize():void;
public function synchronize(string... $modelsToRebuild):void;
}
\ No newline at end of file
......@@ -39,10 +39,12 @@ final class ModelSynchronizer implements IModelSynchronizer {
/**
* Lance la synchronisation
*
* @param string[] $modelsToRebuild
*/
public function synchronize(): void {
public function synchronize(string... $modelsToRebuild): void {
//On met à jour les models du snapshot.
$this->_snapshoter->updateSnapshot();
$this->_snapshoter->updateSnapshot(...$modelsToRebuild);
//On remplace chaque model par sa version la plus récente
//Si des erreurs sont survenues lors de la dernière execution,
//elles sont effacées et le model repart sur une base saine
......
......@@ -5,6 +5,8 @@ use wfw\engine\core\domain\events\IDomainEvent;
use wfw\engine\core\domain\events\EventList;
use wfw\engine\core\domain\events\IDomainEventListener;
use wfw\engine\core\domain\events\IDomainEventObserver;
use wfw\engine\core\domain\events\observers\errors\DomainEventListenerErrorReport;
use wfw\engine\core\domain\events\observers\errors\ListenersFailedToRecieveEvent;
/**
* Gestionnaire d'événements métier
......@@ -26,14 +28,22 @@ class DomainEventObserver implements IDomainEventObserver {
* @param IDomainEvent $e Evenement à dispatcher
*/
public function dispatchDomainEvent(IDomainEvent $e): void {
$errors = [];
foreach($this->_listeners as $listenedEvent=>$listeners){
if($e instanceof $listenedEvent){
foreach($listeners as $listener){
/** @var IDomainEventListener $listener */
$listener->recieveDomainEvent($e);
try{
/** @var IDomainEventListener $listener */
$listener->recieveDomainEvent($e);
}catch(\Error | \Exception $e){
$errors[] = new DomainEventListenerErrorReport($listener,$e);
}
}
}
}
if(($nb = count($errors)) > 0) throw new ListenersFailedToRecieveEvent(
"$nb listeners thrown error while recieving their events.",...$errors
);
}
/**
......
<?php
namespace wfw\engine\core\domain\events\observers\errors;
use wfw\engine\core\domain\events\IDomainEventListener;
/**
* Class DomainEventListenerError
*
* @package wfw\engine\core\domain\events\observers\errors
*/
final class DomainEventListenerErrorReport {
/** @var \Throwable $_error */
private $_error;
/** @var IDomainEventListener $_listener */
private $_listener;
/**
* DomainEventListenerErrorReport constructor.
*
* @param IDomainEventListener $listener Listener that thrown the error
* @param \Throwable $error Error thrown
*/
public function __construct(IDomainEventListener $listener, \Throwable $error) {
$this->_error = $error;
$this->_listener = $listener;
}
/**
* @return \Throwable
*/
public function getError(): \Throwable {
return $this->_error;
}
/**
* @return IDomainEventListener
*/
public function getListener(): IDomainEventListener {
return $this->_listener;
}
}
\ No newline at end of file
<?php
namespace wfw\engine\core\domain\events\observers\errors;
/**
* Thrown when a domain event observer failed in some way
*/
class DomainEventObserverFailure extends \Exception {}
\ No newline at end of file
<?php
namespace wfw\engine\core\domain\events\observers\errors;
use wfw\engine\core\domain\events\IDomainEventListener;
/**
* thrown when some domain event listeners failed to recieve an event
*/
class ListenersFailedToRecieveEvent extends DomainEventObserverFailure {
/** @var DomainEventListenerErrorReport[] $_reports */
private $_reports;
/**
* ListenersFailedToRecieveEvent constructor.
*
* @param string $message
* @param DomainEventListenerErrorReport[] $reports
*/
public function __construct(string $message = "", DomainEventListenerErrorReport... $reports) {
parent::__construct($message);
$this->_reports = $reports;
}
/**
* @return DomainEventListenerErrorReport[]
*/
public function getReports():array{
return $this->_reports;
}
}
\ No newline at end of file
......@@ -112,7 +112,7 @@ final class DBBasedEventStore implements IEventStore {
." Please import the needed class or fixe this event.\n"
." Event data : ".json_encode($event)
);
$res->apply($this->_serializer->unserialize($event["data"]));
$res->apply($event);
}
return $res;
}else{
......
<?php
namespace wfw\engine\core\view;
use wfw\engine\lib\HTML\resources\css\ICSSManager;
use wfw\engine\lib\HTML\resources\js\IJsScriptManager;
use wfw\engine\lib\PHP\errors\IllegalInvocation;
/**
* Classe layout.
*/
......@@ -11,16 +15,32 @@ abstract class Layout extends View implements ILayout {
private $_useViewCacheDirective;
/** @var bool $_allowCache */
private $_allowCache;
/** @var null|IJsScriptManager $_js */
private $_js;
/** @var null|ICSSManager $_css */
private $_css;
/** @var string $_version */
private $_version;
/**
* Layout constructor.
*
* @param null|string $viewPath (optionnel) Chemin vers la vue.
* @param null|string $viewPath (optionnel) Chemin vers la vue.
* @param null|ICSSManager $css
* @param null|IJsScriptManager $js
* @param string $version
*/
public function __construct(?string $viewPath = null) {
public function __construct(
?string $viewPath = null,
?ICSSManager $css = null,
?IJsScriptManager $js = null,
string $version = '0.0'
) {
parent::__construct($viewPath);
$this->_useViewCacheDirective = true;
$this->_allowCache = false;
$this->_css = $css;
$this->_js = $js;
}
/**
......@@ -45,6 +65,20 @@ abstract class Layout extends View implements ILayout {
else return false;
}
/**
* @param string $version
*/
protected function setVersion(string $version): void{
$this->_version = $version;
}
/**
* @return string
*/
protected function getVersion():string{
return $this->_version;
}
/**
* Not take the child views cache policies in consideration anymore and disable cache.
*/
......@@ -67,4 +101,50 @@ abstract class Layout extends View implements ILayout {
public function infos():array{
return [];
}
/**
* @return IJsScriptManager
* @throws IllegalInvocation
*/
public function getJSManager():IJsScriptManager{
if(is_null($this->_js)) throw new IllegalInvocation(
"No JsScriptManager defined in this layout. Please define it through constructor."
);
return $this->_js;
}
/**
* @return ICSSManager
* @throws IllegalInvocation
*/
public function getCssManager():ICSSManager{
if(is_null($this->_css)) throw new IllegalInvocation(
"No CSSManager defined in this layout. Please define it through constructor."
);
return $this->_css;
}
/**
* @return callable
*/
public function getCSSImportCallable():callable{
if(is_null($this->_css)) return function(){};
$cssManager = $this->_css;
$version = sha1($this->_version);
return function(string $key, string $buffer) use ($cssManager,$version):string{
return str_replace($key,$cssManager->write("?v=$version"),$buffer);
};
}
/**
* @return callable
*/
public function getJSImportCallable():callable{
if(is_null($this->_js)) return function(){};
$jsManager = $this->_js;
$version = sha1($this->_version);
return function(string $key,string $buffer)use($jsManager,$version):string{
return str_replace($key,$jsManager->write("?v=$version"),$buffer);
};
}
}
\ No newline at end of file
<?php
namespace wfw\engine\core\view;
use wfw\engine\core\cache\ICacheSystem;
use wfw\engine\core\conf\IConf;
use wfw\engine\core\notifier\INotifier;
use wfw\engine\core\router\IRouter;
use wfw\engine\core\session\ISession;
use wfw\engine\lib\HTML\resources\css\ICSSManager;
use wfw\engine\lib\HTML\resources\js\IJsScriptManager;
use wfw\engine\package\general\lib\JsApiHelper;
use wfw\engine\package\users\domain\types\Admin;
/**
* Default site layout that can be extended.
*/
class SiteLayout extends Layout {
/** @var IConf $_conf */
private $_conf;
/** @var ICacheSystem $_cache */
private $_cache;
/** @var IRouter $_router */
private $_router;
/** @var ISession $_session */
private $_session;
/** @var INotifier $_notifier */
private $_notifier;
/** @var null|string $_adminPanelPath */
private $_adminPanelPath;
/**
* SiteLayout constructor.
*
* @param IConf $conf
* @param IRouter $router
* @param ISession $session
* @param IJsScriptManager $js
* @param ICacheSystem $cache
* @param INotifier $notifier
* @param ICSSManager $css
* @param null|string $adminPanelPath
* @param string $xFrameOptions
* @param null|string $viewPath
*/
public function __construct(
IConf $conf,
IRouter $router,
ISession $session,
IJsScriptManager $js,
ICacheSystem $cache,
INotifier $notifier,
ICSSManager $css,
?string $adminPanelPath = 'JavaScript/web/adminPanel.js',
string $xFrameOptions = "SAMEORIGIN",
?string $viewPath = null
){
parent::__construct($viewPath, $css, $js, $version = sha1(
$conf->getString("server/framework/version")
."-app-".($conf->getString("app/version") ?? '0.0')
));
$this->_conf = $conf;
$this->_cache = $cache;
$this->_router = $router;
$this->_session = $session;
$this->_notifier = $notifier;
$this->_adminPanelPath = $adminPanelPath;
(new JsApiHelper($router,$conf,$session->get("csrfToken"),"?v=$version"))
->register($js);
$this->addHeader("X-FRAME-OPTIONS: $xFrameOptions");
}
/**
* @return string
*/
public function render(): string {
if(!is_null($this->_adminPanelPath)
&& $this->_session->exists("user")
&& $this->_session->get('user')->getType() instanceof Admin
) $this->getJSManager()->register($this->_router->webroot($this->_adminPanelPath));
return parent::render();
}
/**
* @return mixed
*/
public function getConf() {
return $this->_conf;
}
/**
* @return mixed
*/
public function getRouter() {
return $this->_router;
}
/**
* @return mixed
*/
public function getSession() {
return $this->_session;
}
/**
* @return mixed
*/
public function getCache() {
return $this->_cache;
}
/**
* @return mixed
*/
public function getNotifier() {
return $this->_notifier;
}
}
\ No newline at end of file
Markdown is supported
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