diff --git a/src/Storm/Event/Abstract.php b/src/Storm/Event/Abstract.php
new file mode 100644
index 0000000000000000000000000000000000000000..d25da741915d580396e1045db3238df0d2ba504d
--- /dev/null
+++ b/src/Storm/Event/Abstract.php
@@ -0,0 +1,49 @@
+<?php
+/*
+STORM is under the MIT License (MIT)
+
+Copyright (c) 2010-2021 Agence Française Informatique http://www.afi-sa.fr
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+
+
+abstract class Storm_Event_Abstract {
+  protected $_model;
+
+  public function __construct($model) {
+    $this->_model = $model;
+  }
+
+
+  public function getModel() {
+    return $this->_model;
+  }
+
+
+  public function isSaveEvent() {
+    return false;
+  }
+
+
+  public function isDeleteEvent() {
+    return false;
+  }
+}
diff --git a/src/Storm/Event/Delete.php b/src/Storm/Event/Delete.php
new file mode 100644
index 0000000000000000000000000000000000000000..50e7a606849a6c57ec83152102695db49481ba78
--- /dev/null
+++ b/src/Storm/Event/Delete.php
@@ -0,0 +1,32 @@
+<?php
+/*
+STORM is under the MIT License (MIT)
+
+Copyright (c) 2010-2021 Agence Française Informatique http://www.afi-sa.fr
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+
+
+class Storm_Event_Delete extends Storm_Event_Abstract {
+  public function isDeleteEvent() {
+    return true;
+  }
+}
diff --git a/src/Storm/Event/Save.php b/src/Storm/Event/Save.php
index 2a1267a86f257c2985ceb9313055058057e656e7..87a0f413f23184c0452c4dd1ad1023cf0c8b16b7 100644
--- a/src/Storm/Event/Save.php
+++ b/src/Storm/Event/Save.php
@@ -25,15 +25,8 @@ THE SOFTWARE.
 */
 
 
-class Storm_Event_Save {
-  protected $_model;
-
-  public function __construct($model) {
-    $this->_model = $model;
-  }
-
-
-  public function getModel() {
-    return $this->_model;
+class Storm_Event_Save extends Storm_Event_Abstract {
+  public function isSaveEvent() {
+    return true;
   }
-}
+}
\ No newline at end of file
diff --git a/src/Storm/Model/Abstract.php b/src/Storm/Model/Abstract.php
index 7bedff6264868c7aeea0d65246c204dcf48d5a9d..93a4964faa7988ec605a0231fd5805f34fe3ee6c 100644
--- a/src/Storm/Model/Abstract.php
+++ b/src/Storm/Model/Abstract.php
@@ -262,12 +262,13 @@ abstract class Storm_Model_Abstract {
 
 
   /**
+   * @param $force_primary_key bool
    * @return bool
    */
-  public function save() {
+  public function save($force_primary_key = false) {
     $this->_updateNullBelongsToIdFieldsFromDependents();
     if ($valid = $this->isValid()) {
-      $this->saveWithoutValidation();
+      $this->saveWithoutValidation($force_primary_key);
       $this->_getEvents()->notify(new Storm_Event_Save($this));
       $this->_attributes_in_db = $this->_attributes;
     }
@@ -281,8 +282,8 @@ abstract class Storm_Model_Abstract {
   }
 
 
-  public function assertSave() {
-    if (!$this->save())
+  public function assertSave($force_primary_key = false) {
+    if (!$this->save($force_primary_key))
       throw new Storm_Model_Exception('Can\'t save '.get_class($this).': '.implode(',',$this->getErrors()));
     return true;
   }
@@ -292,17 +293,21 @@ abstract class Storm_Model_Abstract {
   }
 
 
-  public function saveWithoutValidation() {
+  public function saveWithoutValidation($force_primary_key = false) : self {
     $this->beforeSave();
 
     $this->_associations->saveBefore($this);
 
     $this->_updateNullBelongsToIdFieldsFromDependents();
 
-    $this->getLoader()->save($this);
+    $force_primary_key
+      ? $this->getLoader()->saveForcePrimaryKey($this)
+      : $this->getLoader()->save($this);
+
     $this->_saveDependencies();
 
     $this->afterSave();
+    return $this;
   }
 
 
@@ -522,6 +527,7 @@ abstract class Storm_Model_Abstract {
 
     if ($this->isNew() || $this->getLoader()->delete($this)) {
       $this->afterDelete();
+      $this->_getEvents()->notify(new Storm_Event_Delete($this));
     }
   }
 
diff --git a/src/Storm/Model/Loader.php b/src/Storm/Model/Loader.php
index 962d2a127e51f4c13a7810e2573d698fdbbf5664..05a73fbf18fb0829580d851c4017a793ee317370 100644
--- a/src/Storm/Model/Loader.php
+++ b/src/Storm/Model/Loader.php
@@ -266,7 +266,7 @@ class Storm_Model_Loader {
 
   public function newInstanceWithIdAssertSave($id, $attributes = null) {
     $instance = $this->newInstanceWithId($id,$attributes);
-    $instance->assertSave();
+    $instance->assertSave(true);
     return $instance;
   }
 
@@ -298,37 +298,20 @@ class Storm_Model_Loader {
    * @return int
    */
   public function save($model) {
-    $data = array_filter($model->attributesToArray(),
-                         [$this, 'isValueCanBeSaved']);
-    $id = isset($data['id']) ? $data['id'] : null;
-    unset($data['id']);
-
-    if ($model->isNew()) {
-      if ($result = $this->getPersistenceStrategy()->insert($data)) {
-        $insert_id = ($model->isFixedId()) ?
-          $data[$this->_id_field] :
-          $this->getPersistenceStrategy()->lastInsertId();
-
-        $model->setId($insert_id);
-      }
-
-      return $result;
-    }
-
-    $data[$this->_id_field] = $id;
-
-    return $this->getPersistenceStrategy()
-      ->update($data, $id);
-
+    return (new Storm_Model_Loader_SaveOperation($this))->save($model);
   }
 
 
-
   /**
-   * @return false if value is not saveable in db
+   * Save model and force primary key to be written / skip autoincrement
    */
+  public function saveForcePrimaryKey(Storm_Model_Abstract $model) : int {
+    return (new Storm_Model_Loader_SaveForcePrimaryKeyOperation($this))->save($model);
+  }
+
+
   public function isValueCanBeSaved($value) {
-    return is_string($value) || is_int($value) || is_float($value) || is_bool($value) || (null === $value);
+    return (new Storm_Model_Loader_SaveOperation($this))->isValueCanBeSaved($value);
   }
 
 
diff --git a/src/Storm/Model/Loader/SaveForcePrimaryKeyOperation.php b/src/Storm/Model/Loader/SaveForcePrimaryKeyOperation.php
new file mode 100644
index 0000000000000000000000000000000000000000..75319bd5d68221a3558c38fdfd4e1d01517c4d0a
--- /dev/null
+++ b/src/Storm/Model/Loader/SaveForcePrimaryKeyOperation.php
@@ -0,0 +1,39 @@
+<?php
+/**
+STORM is under the MIT License (MIT)
+
+Copyright (c) 2010-2021 Agence Française Informatique http://www.afi-sa.fr
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+
+class Storm_Model_Loader_SaveForcePrimaryKeyOperation extends Storm_Model_Loader_SaveOperation {
+  protected function _isNew(Storm_Model_Abstract $model) : bool {
+    return 0 === $this->_loader->countBy([$this->_id_field => $this->_id]);
+  }
+
+
+  protected function _saveNew(Storm_Model_Abstract $model) {
+    if ($result = $this->_persistence_strategy->insert($this->_datas))
+      $model->setId($this->_id);
+
+    return $result;
+  }
+}
\ No newline at end of file
diff --git a/src/Storm/Model/Loader/SaveOperation.php b/src/Storm/Model/Loader/SaveOperation.php
new file mode 100644
index 0000000000000000000000000000000000000000..39c598dc4ae56f9644b3a2ca01b7b1153b7c8a44
--- /dev/null
+++ b/src/Storm/Model/Loader/SaveOperation.php
@@ -0,0 +1,96 @@
+<?php
+/**
+STORM is under the MIT License (MIT)
+
+Copyright (c) 2010-2021 Agence Française Informatique http://www.afi-sa.fr
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+
+class Storm_Model_Loader_SaveOperation {
+  protected
+    $_loader,
+    $_persistence_strategy,
+    $_id_field,
+    $_datas;
+
+  public function __construct(Storm_Model_Loader $loader) {
+    $this->_loader = $loader;
+    $this->_persistence_strategy = $loader->getPersistenceStrategy();
+    $this->_id_field = $loader->getIdField();
+  }
+
+
+  /**
+   * @return false if value is not saveable in db
+   */
+  public function isValueCanBeSaved($value) : bool {
+    return is_string($value) || is_int($value) || is_float($value) || is_bool($value) || (null === $value);
+  }
+
+
+  protected function _prepareSaveDatas(Storm_Model_Abstract $model) : self {
+    $this->_datas = array_filter($model->attributesToArray(),
+                                 [$this, 'isValueCanBeSaved']);
+    $this->_id = isset($this->_datas['id'])
+      ? $this->_datas['id']
+      : null;
+
+    if ('id' !== $this->_id_field)
+      unset($this->_datas['id']);
+
+    return $this;
+  }
+
+
+  public function save(Storm_Model_Abstract $model) {
+    $this->_prepareSaveDatas($model);
+
+    return $this->_isNew($model)
+      ? $this->_saveNew($model)
+      : $this->_saveExisting($model);
+  }
+
+
+  protected function _isNew(Storm_Model_Abstract $model) : bool {
+    return $model->isNew();
+  }
+
+
+  protected function _saveExisting(Storm_Model_Abstract $model) {
+    $this->_datas[$this->_id_field] = $this->_id;
+
+    return $this->_persistence_strategy
+      ->update($this->_datas, $this->_id);
+  }
+
+
+  protected function _saveNew(Storm_Model_Abstract $model) {
+    if ($result = $this->_persistence_strategy->insert($this->_datas)) {
+      $insert_id = ($model->isFixedId())
+        ? $this->_datas[$this->_id_field]
+        : $this->_persistence_strategy->lastInsertId();
+
+      $model->setId($insert_id);
+    }
+
+    return $result;
+  }
+}
diff --git a/src/Storm/Model/PersistenceStrategy/Volatile.php b/src/Storm/Model/PersistenceStrategy/Volatile.php
index 33d80f5ded48bb95ae36314020bfdf0be765182a..96f0b032f2169d5874eace0ec30a14c7d9b3149a 100644
--- a/src/Storm/Model/PersistenceStrategy/Volatile.php
+++ b/src/Storm/Model/PersistenceStrategy/Volatile.php
@@ -262,8 +262,8 @@ class Storm_Model_PersistenceStrategy_Volatile  extends Storm_Model_PersistenceS
     $instance = $this->_loader->newInstance();
     $instance->updateAttributes($data);
 
-    $id = $instance->isFixedId() ?
-      $instance->callGetterByAttributeName($this->_loader->getIdField())
+    $id = isset($data[$this->_loader->getIdField()])
+      ? $data[$this->_loader->getIdField()]
       : $this->lastInsertId() + 1;
 
     $instance->setId($id);
@@ -271,6 +271,7 @@ class Storm_Model_PersistenceStrategy_Volatile  extends Storm_Model_PersistenceS
     return true;
   }
 
+
   public function lastInsertId() {
     if (sizeof($this->_instances)<1)
       return 0;
@@ -280,14 +281,14 @@ class Storm_Model_PersistenceStrategy_Volatile  extends Storm_Model_PersistenceS
 
   public function update($data,$id) {
     if (!isset($this->_instances[$id]))
-      $instance = $this->_loader->newInstance();
-    else
-      $instance = $this->_instances[$id];
+      return 0;
+
+    $instance = $this->_instances[$id];
     $instance
       ->updateAttributes($data)
       ->setId($id);
     $this->_instances[$id]=$instance;
-    return $instance;
+    return 1;
   }
 
 
diff --git a/src/Storm/Test/THelpers.php b/src/Storm/Test/THelpers.php
index db1913fdb8b645a270d55fac4e4253bcf5f1b9a5..13fd996570599d2446853e53341c74500319c0e1 100644
--- a/src/Storm/Test/THelpers.php
+++ b/src/Storm/Test/THelpers.php
@@ -46,9 +46,7 @@ trait Storm_Test_THelpers {
   public static function fixture($class_name, $attributes) {
     $class_name::beVolatile();
 
-    $instance = $class_name::newInstanceWithId($attributes['id'], $attributes);
-    $instance->assertSave();
-    return $instance;
+    return $class_name::newInstanceWithIdAssertSave($attributes['id'], $attributes);
   }
 }
 
diff --git a/tests/Storm/Model/EventTriggeringTest.php b/tests/Storm/Model/EventTriggeringTest.php
index 046ab262a351636f33698b477f74e981b7826f36..b1331b238277489aec6565e7603128b501bc503a 100644
--- a/tests/Storm/Model/EventTriggeringTest.php
+++ b/tests/Storm/Model/EventTriggeringTest.php
@@ -52,6 +52,16 @@ abstract class Storm_Model_EventTriggeringTestCase extends Storm_Test_ModelTestC
   }
 
 
+  /** @test */
+  public function withRegisterdClosureShouldCallBackOnDelete() {
+    $model = new Storm_Model_EventTriggeringModel;
+    $model->save();
+    $this->_events->register($this->_callable);
+    $model->delete();
+    $this->assertTrue($this->_called);
+  }
+
+
   /** @test */
   public function withUnregisteredClosureShouldNotCallbackOnSave() {
     $this->_events->register($this->_callable)
diff --git a/tests/Storm/Test/LoaderTest.php b/tests/Storm/Test/LoaderTest.php
index f2d4fe78bf353caa6ec50663bb5092af13c47f5b..cdcab35d548c199863efbdfdd0f7aee593e257c2 100644
--- a/tests/Storm/Test/LoaderTest.php
+++ b/tests/Storm/Test/LoaderTest.php
@@ -21,11 +21,13 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
-
 */
 
-class Storm_Test_LoaderTest extends Storm_Test_ModelTestCase {
-  protected $_loader, $_table;
+
+abstract class Storm_Test_LoaderTestCase extends Storm_Test_ModelTestCase {
+  protected
+    $_loader,
+    $_table;
 
   public function setUp() {
     parent::setUp();
@@ -44,13 +46,16 @@ class Storm_Test_LoaderTest extends Storm_Test_ModelTestCase {
                                                         $value))
                                     :  "'" . $value . "'",
                                     $clause);
-               })
-      ;
+               });
 
     $this->_loader = Storm_Test_Mock_User::getLoader()->setTable($this->_table);
   }
+}
+
 
 
+
+class Storm_Test_LoaderBasicTest extends Storm_Test_LoaderTestCase {
   /** @test */
   public function findAllShouldAcceptASQLQuery() {
     $this->_table->whenCalled('fetchAll')->answers([]);
@@ -228,3 +233,60 @@ class Storm_Test_LoaderTest extends Storm_Test_ModelTestCase {
     $this->assertEquals('nom = "dupont"', $this->_table->getFirstAttributeForLastCallOn('delete'));
   }
 }
+
+
+
+
+class Storm_Test_LoaderSaveWithIdTest extends Storm_Test_LoaderTestCase {
+  protected $_select;
+
+  public function setUp() {
+    parent::setUp();
+    $this->_table
+      ->whenCalled('select')
+      ->answers($this->_select = $this->mock());
+
+    $this->_select
+      ->whenCalled('where')->with('id=?', '4')->answers($this->_select)
+      ->whenCalled('from')->with($this->_table, ['count(*) as numberof'])->answers($this->_select);
+
+  }
+
+
+  protected function _fetchAllAnswersNumberOf($numberof) {
+    $this->_table
+      ->whenCalled('fetchAll')
+      ->with($this->_select)
+      ->answers(new Zend_Db_Table_Rowset(['data' => [['numberof' => $numberof]]]));
+    return $this->_table;
+  }
+
+
+  /** @test */
+  public function saveWithIdOnNewInstanceShouldExecuteInsertStatement() {
+    $this
+      ->_fetchAllAnswersNumberOf(0)
+      ->whenCalled('insert')
+      ->answers(1);
+
+    Storm_Test_Mock_User::newInstanceWithId(4,
+                                            [])->save($force_primary_key = true);
+    $this->assertArraySubset(['id' => 4],
+                             $this->_table->getFirstAttributeForLastCallOn('insert'));
+  }
+
+
+  /** @test */
+  public function saveWithIdOnExistingInstanceInDbShouldExecuteUpdateStatement() {
+    $this
+      ->_fetchAllAnswersNumberOf(1)
+      ->whenCalled('update')
+      ->answers(1);
+
+
+    Storm_Test_Mock_User::newInstanceWithId(4,
+                                            [])->save($force_primary_key = true);
+    $this->assertArraySubset(['id' => 4],
+                             $this->_table->getFirstAttributeForLastCallOn('update'));
+  }
+}
\ No newline at end of file
diff --git a/tests/Storm/Test/LoaderVolatileTest.php b/tests/Storm/Test/LoaderVolatileTest.php
index 92d242b1afd5709f2745fc20275c273d23ebe4a8..2b8c14958ca1cb879bedf5ffe164cfc34806331f 100644
--- a/tests/Storm/Test/LoaderVolatileTest.php
+++ b/tests/Storm/Test/LoaderVolatileTest.php
@@ -75,6 +75,11 @@ class Storm_Test_VolatileCat extends Storm_Model_Abstract {
     $_default_attribute_values = ['name' => ''],
 
     $_belongs_to = ['master' => ['model' => Storm_Test_VolatileUser::class]];
+
+
+  public function validate() {
+    $this->checkAttribute('name', $this->getName() !== 'dog', 'Cannot call a cat dog');
+  }
 }
 
 
@@ -733,5 +738,17 @@ class Storm_Test_LoaderVolatileTest extends Storm_Test_ModelTestCase {
     $this->assertEquals([$this->albert],
                         Storm_Test_VolatileUser::findAllBy(['login like' => '%albert%']));
   }
+
+
+  /**
+   * @test
+   * @expectedException Storm_Model_Exception
+   * @expectedExceptionMessage Can't save Storm_Test_VolatileCat: Cannot call a cat dog
+   */
+  public function invalidFixtureShouldThrowException() {
+    $this->fixture(Storm_Test_VolatileCat::class,
+                   ['id' => 764,
+                    'name' => 'dog']);
+  }
 }
 ?>